145 lines
4.6 KiB
Kotlin
145 lines
4.6 KiB
Kotlin
/*
|
|
* 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.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() {}
|
|
|
|
/**
|
|
* The UUID of this entity.
|
|
*
|
|
* If the entity is a player and the server uses authentication, it is the player's UUID, if it is not,
|
|
* it is a UUIDv3 generated from a string consisting of "OfflinePlayer:" and the player's username.
|
|
*
|
|
* 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<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()
|
|
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")
|
|
|
|
/**
|
|
* 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)
|
|
}
|
|
|
|
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>
|
|
}
|