Archived
1
0
Fork 0

Register entities globally

This commit is contained in:
Moritz Ruth 2021-01-05 20:05:38 +01:00
parent 3fb9cc3298
commit cb50c7bb39
No known key found for this signature in database
GPG key ID: AFD57E23E753841B
9 changed files with 79 additions and 56 deletions

View file

@ -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

View file

@ -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")
}
}

View file

@ -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)
}

View file

@ -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

View file

@ -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

View file

@ -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")
}

View file

@ -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()

View file

@ -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()

View file

@ -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()