Archived
1
0
Fork 0
This repository has been archived on 2025-03-02. You can view files and clone it, but cannot push or open issues or pull requests.
uranos/uranos-api/src/main/kotlin/space/uranos/entity/Entity.kt

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