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