Register entities globally
This commit is contained in:
parent
3fb9cc3298
commit
cb50c7bb39
9 changed files with 79 additions and 56 deletions
|
@ -12,13 +12,13 @@
|
|||
- Players can have items (with metadata) in their inventory
|
||||
- Inventories
|
||||
- World modifications are sent to players
|
||||
- Crafting
|
||||
- PlayerInteract events are emitted when blocks are clicked
|
||||
- Command framework + permissions
|
||||
- Commands can be sent from the console
|
||||
- Players can be teleported between worlds
|
||||
- Entity AI framework
|
||||
- Scoreboards + Teams
|
||||
- Crafting
|
||||
|
||||
## Conventions
|
||||
|
||||
|
|
|
@ -8,7 +8,6 @@ package space.uranos.testplugin.anvil
|
|||
import com.google.common.cache.CacheBuilder
|
||||
import com.google.common.cache.CacheLoader
|
||||
import com.google.common.cache.LoadingCache
|
||||
import space.uranos.entity.Entity
|
||||
import space.uranos.world.Chunk
|
||||
import space.uranos.world.Dimension
|
||||
import space.uranos.world.World
|
||||
|
@ -19,7 +18,6 @@ class AnvilWorld(
|
|||
override val isFlat: Boolean
|
||||
) : World(UUID.randomUUID()) {
|
||||
override val loadedChunks: Map<Chunk.Key, Chunk> get() = chunks.asMap().filter { (_, chunk) -> chunk.loaded }
|
||||
override val entities = emptyList<Entity>()
|
||||
|
||||
private val chunks: LoadingCache<Chunk.Key, Chunk> = CacheBuilder.newBuilder()
|
||||
.build(object : CacheLoader<Chunk.Key, Chunk>() {
|
||||
|
@ -29,8 +27,4 @@ class AnvilWorld(
|
|||
})
|
||||
|
||||
override fun getChunk(key: Chunk.Key): Chunk = chunks.get(key)
|
||||
|
||||
override fun spawnEntity(entity: Entity) {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,15 +5,13 @@
|
|||
|
||||
package space.uranos.entity
|
||||
|
||||
import kotlinx.coroutines.sync.Mutex
|
||||
import kotlinx.coroutines.sync.withLock
|
||||
import space.uranos.Uranos
|
||||
import space.uranos.world.World
|
||||
import java.util.*
|
||||
|
||||
abstract class Entity internal constructor() {
|
||||
/**
|
||||
* An integer unique to this entity which will not be persisted, for example when the entity is serialized.
|
||||
*/
|
||||
val uid: Int = Uranos.claimEntityID()
|
||||
|
||||
/**
|
||||
* The UUID of this entity.
|
||||
*
|
||||
|
@ -25,4 +23,28 @@ abstract class Entity internal constructor() {
|
|||
open val uuid: UUID = UUID.randomUUID()
|
||||
|
||||
abstract val type: EntityType
|
||||
|
||||
private val worldMutex = Mutex()
|
||||
var world: World? = null; private set
|
||||
|
||||
suspend fun setWorld(world: World?) {
|
||||
if (world == this.world) return
|
||||
|
||||
worldMutex.withLock {
|
||||
this.world?.internalEntities?.remove(this)
|
||||
this.world = world
|
||||
world?.internalEntities?.add(this)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns [world] if it is not null, otherwise throws [IllegalStateException].
|
||||
*/
|
||||
fun getWorldOrFail() = world ?: throw IllegalStateException("This entity has not been spawned")
|
||||
|
||||
/**
|
||||
* An integer unique to this entity which will not be persisted, for example when the entity is serialized.
|
||||
*/
|
||||
@Suppress("LeakingThis")
|
||||
val uid: Int = Uranos.registerEntity(this)
|
||||
}
|
||||
|
|
|
@ -14,9 +14,6 @@ import space.uranos.world.World
|
|||
import java.util.*
|
||||
import kotlin.coroutines.CoroutineContext
|
||||
|
||||
/**
|
||||
* A **real** player.
|
||||
*/
|
||||
interface Player {
|
||||
val coroutineContext: CoroutineContext get() = session.coroutineContext
|
||||
|
||||
|
@ -107,7 +104,7 @@ interface Player {
|
|||
var playerListName: TextComponent?
|
||||
var compassTarget: VoxelLocation
|
||||
|
||||
val currentlyViewedChunks: List<Chunk>
|
||||
val currentlyViewedChunks: Collection<Chunk>
|
||||
|
||||
companion object {
|
||||
const val DEFAULT_FIELD_OF_VIEW = 0.1f
|
||||
|
|
|
@ -8,6 +8,7 @@ package space.uranos.server
|
|||
import space.uranos.Registry
|
||||
import space.uranos.Scheduler
|
||||
import space.uranos.command.Command
|
||||
import space.uranos.entity.Entity
|
||||
import space.uranos.event.EventBus
|
||||
import space.uranos.event.EventHandlerPositionManager
|
||||
import space.uranos.logging.Logger
|
||||
|
@ -19,55 +20,66 @@ import space.uranos.recipe.Recipe
|
|||
import space.uranos.world.BiomeRegistry
|
||||
import space.uranos.world.Dimension
|
||||
import java.io.File
|
||||
import java.util.*
|
||||
import java.util.concurrent.atomic.AtomicInteger
|
||||
import kotlin.coroutines.CoroutineContext
|
||||
|
||||
interface Server {
|
||||
val eventBus: EventBus
|
||||
val eventHandlerPositions: EventHandlerPositionManager
|
||||
abstract class Server {
|
||||
abstract val eventBus: EventBus
|
||||
abstract val eventHandlerPositions: EventHandlerPositionManager
|
||||
|
||||
/**
|
||||
* [CoroutineContext] confined to the server thread.
|
||||
*
|
||||
* Is cancelled when the server is shutting down.
|
||||
*/
|
||||
val coroutineContext: CoroutineContext
|
||||
abstract val coroutineContext: CoroutineContext
|
||||
|
||||
/**
|
||||
* All sessions connected to the server.
|
||||
*/
|
||||
val sessions: Collection<Session>
|
||||
abstract val sessions: Collection<Session>
|
||||
|
||||
/**
|
||||
* All players connected to the server.
|
||||
*/
|
||||
val players: Collection<Player>
|
||||
abstract val players: Collection<Player>
|
||||
|
||||
val pluginManager: PluginManager
|
||||
val serverDirectory: File
|
||||
abstract val pluginManager: PluginManager
|
||||
abstract val serverDirectory: File
|
||||
val cacheDirectory: File get() = serverDirectory.resolve("cache")
|
||||
|
||||
val dimensionRegistry: Registry<Dimension>
|
||||
val recipeRegistry: Registry<Recipe>
|
||||
val commandRegistry: Registry<Command>
|
||||
val biomeRegistry: BiomeRegistry
|
||||
abstract val dimensionRegistry: Registry<Dimension>
|
||||
abstract val recipeRegistry: Registry<Recipe>
|
||||
abstract val commandRegistry: Registry<Command>
|
||||
abstract val biomeRegistry: BiomeRegistry
|
||||
|
||||
val loggingOutputProvider: LoggingOutputProvider
|
||||
val scheduler: Scheduler
|
||||
abstract val loggingOutputProvider: LoggingOutputProvider
|
||||
abstract val scheduler: Scheduler
|
||||
|
||||
val developmentMode: Boolean
|
||||
val minimumLogLevel: Logger.Level
|
||||
|
||||
/**
|
||||
* Returns an unused entity ID.
|
||||
*
|
||||
* **Should not be used by plugins.**
|
||||
*/
|
||||
fun claimEntityID(): Int
|
||||
abstract val developmentMode: Boolean
|
||||
abstract val minimumLogLevel: Logger.Level
|
||||
|
||||
/**
|
||||
* Initiates shutting down the server.
|
||||
*/
|
||||
fun shutdown()
|
||||
abstract fun shutdown()
|
||||
|
||||
/**
|
||||
* 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 nextEntityID = AtomicInteger()
|
||||
|
||||
/**
|
||||
* Returns the UID for [entity].
|
||||
*/
|
||||
internal fun registerEntity(entity: Entity): Int {
|
||||
entities.add(entity)
|
||||
return nextEntityID.getAndIncrement()
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val TICKS_PER_SECOND = 20
|
||||
|
|
|
@ -6,3 +6,7 @@
|
|||
package space.uranos.util
|
||||
|
||||
fun Int.clamp(range: IntRange) = maxOf(minOf(range.first, range.last), minOf(maxOf(range.first, range.last), this))
|
||||
|
||||
fun clampArgument(name: String, range: IntRange, actualValue: Int) {
|
||||
if (!range.contains(actualValue)) throw IllegalArgumentException("$name must be in $range")
|
||||
}
|
||||
|
|
|
@ -15,6 +15,7 @@ import space.uranos.util.newSingleThreadDispatcher
|
|||
import space.uranos.util.supervisorChild
|
||||
import space.uranos.util.untilPossiblyNegative
|
||||
import java.util.*
|
||||
import java.util.concurrent.CopyOnWriteArraySet
|
||||
import kotlin.coroutines.CoroutineContext
|
||||
|
||||
/**
|
||||
|
@ -45,9 +46,11 @@ abstract class World(val uuid: UUID) {
|
|||
open val seed: Long? = null
|
||||
|
||||
/**
|
||||
* All entities in this world. Use [spawnEntity] for spawning new ones.
|
||||
* All entities in this world.
|
||||
*/
|
||||
abstract val entities: Collection<Entity>
|
||||
internal val internalEntities = CopyOnWriteArraySet<Entity>()
|
||||
|
||||
val entities get() = internalEntities.toList()
|
||||
|
||||
abstract fun getChunk(key: Chunk.Key): Chunk
|
||||
|
||||
|
@ -66,7 +69,6 @@ abstract class World(val uuid: UUID) {
|
|||
*
|
||||
* @param hollow Whether the cube is hollow
|
||||
*/
|
||||
@OptIn(ExperimentalStdlibApi::class)
|
||||
fun getVoxelsInCube(cornerA: VoxelLocation, cornerB: VoxelLocation, hollow: Boolean = false): List<Voxel> {
|
||||
fun getList(a: Int, b: Int) =
|
||||
if (hollow) listOf(a, b).distinct()
|
||||
|
@ -95,11 +97,6 @@ abstract class World(val uuid: UUID) {
|
|||
TODO()
|
||||
}
|
||||
|
||||
/**
|
||||
* Spawns [entity][Entity] in this world.
|
||||
*/
|
||||
abstract fun spawnEntity(entity: Entity)
|
||||
|
||||
suspend fun destroy() = withContext(coroutineContext) {
|
||||
// TODO: Move or kick players
|
||||
coroutineContext.cancel()
|
||||
|
|
|
@ -28,13 +28,12 @@ import space.uranos.world.Dimension
|
|||
import java.io.File
|
||||
import java.security.KeyPair
|
||||
import java.util.concurrent.Executors
|
||||
import java.util.concurrent.atomic.AtomicInteger
|
||||
import kotlin.coroutines.CoroutineContext
|
||||
import kotlin.system.exitProcess
|
||||
|
||||
// TODO: Consider using DI because this improves testability
|
||||
|
||||
class UranosServer internal constructor() : Server {
|
||||
class UranosServer internal constructor() : Server() {
|
||||
val logger = Logger("Server")
|
||||
private val socketServer = UranosSocketServer(this)
|
||||
|
||||
|
@ -90,9 +89,6 @@ class UranosServer internal constructor() : Server {
|
|||
override val eventBus = UranosEventBus(developmentMode)
|
||||
override val eventHandlerPositions = UranosEventHandlerPositionManager()
|
||||
|
||||
private val nextEntityID = AtomicInteger()
|
||||
override fun claimEntityID() = nextEntityID.incrementAndGet()
|
||||
|
||||
override fun shutdown() {
|
||||
runBlocking {
|
||||
scheduler.shutdown()
|
||||
|
|
|
@ -10,6 +10,7 @@ import space.uranos.chat.TextComponent
|
|||
import space.uranos.net.Session
|
||||
import space.uranos.net.packet.play.ChunkDataPacket
|
||||
import space.uranos.net.packet.play.ChunkLightDataPacket
|
||||
import space.uranos.util.clampArgument
|
||||
import space.uranos.world.Chunk
|
||||
import space.uranos.world.VoxelLocation
|
||||
import space.uranos.world.World
|
||||
|
@ -35,8 +36,8 @@ class UranosPlayer(
|
|||
) : Player {
|
||||
override var selectedHotbarSlot = 0
|
||||
set(value) {
|
||||
if (value in 0..8) field = value
|
||||
else throw IllegalArgumentException("selectedHotbarSlot must be in 0..8")
|
||||
clampArgument("selectedHotbarSlot", 0..8, value)
|
||||
field = value
|
||||
}
|
||||
|
||||
init {
|
||||
|
@ -44,7 +45,7 @@ class UranosPlayer(
|
|||
}
|
||||
|
||||
override var playerListName: TextComponent? = null
|
||||
override var currentlyViewedChunks: List<Chunk> = emptyList()
|
||||
override var currentlyViewedChunks = emptyList<Chunk>()
|
||||
|
||||
init {
|
||||
updateCurrentlyViewedChunks()
|
||||
|
|
Reference in a new issue