268 lines
7.1 KiB
Kotlin
268 lines
7.1 KiB
Kotlin
package ru.dbotthepony.kstarbound.world
|
||
|
||
import it.unimi.dsi.fastutil.objects.ObjectArrayList
|
||
import it.unimi.dsi.fastutil.objects.ObjectArraySet
|
||
import it.unimi.dsi.fastutil.objects.ReferenceOpenHashSet
|
||
import ru.dbotthepony.kommons.arrays.Object2DArray
|
||
import ru.dbotthepony.kommons.util.AABB
|
||
import ru.dbotthepony.kommons.vector.Vector2d
|
||
import ru.dbotthepony.kstarbound.network.LegacyNetworkCellState
|
||
import ru.dbotthepony.kstarbound.world.api.AbstractCell
|
||
import ru.dbotthepony.kstarbound.world.api.ICellAccess
|
||
import ru.dbotthepony.kstarbound.world.api.ImmutableCell
|
||
import ru.dbotthepony.kstarbound.world.api.OffsetCellAccess
|
||
import ru.dbotthepony.kstarbound.world.api.TileView
|
||
import ru.dbotthepony.kstarbound.world.entities.AbstractEntity
|
||
import ru.dbotthepony.kstarbound.world.entities.DynamicEntity
|
||
import ru.dbotthepony.kstarbound.world.entities.TileEntity
|
||
import java.util.concurrent.CompletableFuture
|
||
import kotlin.concurrent.withLock
|
||
|
||
/**
|
||
* Чанк мира
|
||
*
|
||
* Хранит в себе тайлы и ентити внутри себя
|
||
*
|
||
* Считается, что один тайл имеет форму квадрата и сторона квадрата примерно равна полуметру,
|
||
* что будет называться Starbound Unit
|
||
*
|
||
* Весь игровой мир будет измеряться в Starbound Unit'ах
|
||
*/
|
||
abstract class Chunk<WorldType : World<WorldType, This>, This : Chunk<WorldType, This>>(
|
||
val world: WorldType,
|
||
val pos: ChunkPos,
|
||
) : ICellAccess {
|
||
var changeset = 0
|
||
private set
|
||
|
||
var tileChangeset = 0
|
||
private set
|
||
var liquidChangeset = 0
|
||
private set
|
||
var cellChangeset = 0
|
||
private set
|
||
|
||
var foregroundChangeset = 0
|
||
private set
|
||
var backgroundChangeset = 0
|
||
private set
|
||
|
||
val entities = ReferenceOpenHashSet<AbstractEntity>()
|
||
val dynamicEntities = ReferenceOpenHashSet<DynamicEntity>()
|
||
val tileEntities = ReferenceOpenHashSet<TileEntity>()
|
||
protected val subscribers = ObjectArraySet<IChunkListener>()
|
||
|
||
// local cells' tile access
|
||
val localBackgroundView = TileView.Background(this)
|
||
val localForegroundView = TileView.Foreground(this)
|
||
|
||
// relative world cells access (accessing 0, 0 will lookup cell in world, relative to this chunk)
|
||
val worldView = OffsetCellAccess(world, pos.x * CHUNK_SIZE, pos.y * CHUNK_SIZE)
|
||
val worldBackgroundView = TileView.Background(worldView)
|
||
val worldForegroundView = TileView.Foreground(worldView)
|
||
|
||
val aabb = aabbBase + Vector2d(pos.x * CHUNK_SIZE.toDouble(), pos.y * CHUNK_SIZE.toDouble())
|
||
|
||
protected val cells = lazy {
|
||
Object2DArray(CHUNK_SIZE, CHUNK_SIZE, AbstractCell.NULL)
|
||
}
|
||
|
||
fun legacyNetworkCells(): Object2DArray<LegacyNetworkCellState> {
|
||
if (cells.isInitialized()) {
|
||
val cells = cells.value
|
||
return Object2DArray(CHUNK_SIZE, CHUNK_SIZE) { a, b -> cells[a, b].toLegacyNet() }
|
||
} else {
|
||
return Object2DArray(CHUNK_SIZE, CHUNK_SIZE, LegacyNetworkCellState.NULL)
|
||
}
|
||
}
|
||
|
||
fun loadCells(source: Object2DArray<out AbstractCell>) {
|
||
val ours = cells.value
|
||
source.checkSizeEquals(ours)
|
||
|
||
for (x in 0 until CHUNK_SIZE) {
|
||
for (y in 0 until CHUNK_SIZE) {
|
||
ours[x, y] = source[x, y].immutable()
|
||
}
|
||
}
|
||
}
|
||
|
||
override fun getCell(x: Int, y: Int): AbstractCell {
|
||
if (!cells.isInitialized())
|
||
return AbstractCell.NULL
|
||
|
||
return cells.value[x, y]
|
||
}
|
||
|
||
override fun getCellDirect(x: Int, y: Int): AbstractCell {
|
||
return getCell(x, y)
|
||
}
|
||
|
||
override fun setCell(x: Int, y: Int, cell: AbstractCell): Boolean {
|
||
val ix = x and CHUNK_SIZE_FF
|
||
val iy = y and CHUNK_SIZE_FF
|
||
|
||
if (ix != x || iy != y) return false
|
||
|
||
val old = if (cells.isInitialized()) cells.value[ix, iy] else AbstractCell.NULL
|
||
val new = cell.immutable()
|
||
|
||
if (old != new) {
|
||
cells.value[ix, iy] = new
|
||
|
||
if (old.foreground != new.foreground) {
|
||
foregroundChanges(ix, iy, new)
|
||
}
|
||
|
||
if (old.background != new.background) {
|
||
backgroundChanges(ix, iy, new)
|
||
}
|
||
|
||
if (old.liquid != new.liquid) {
|
||
liquidChanges(ix, iy, new)
|
||
}
|
||
|
||
cellChanges(ix, iy, new)
|
||
}
|
||
|
||
return true
|
||
}
|
||
|
||
protected open fun foregroundChanges(x: Int, y: Int, cell: ImmutableCell) {
|
||
cellChanges(x, y, cell)
|
||
tileChangeset++
|
||
foregroundChangeset++
|
||
}
|
||
|
||
protected open fun backgroundChanges(x: Int, y: Int, cell: ImmutableCell) {
|
||
cellChanges(x, y, cell)
|
||
tileChangeset++
|
||
backgroundChangeset++
|
||
}
|
||
|
||
protected open fun liquidChanges(x: Int, y: Int, cell: ImmutableCell) {
|
||
cellChanges(x, y, cell)
|
||
liquidChangeset++
|
||
}
|
||
|
||
protected open fun cellChanges(x: Int, y: Int, cell: ImmutableCell) {
|
||
changeset++
|
||
cellChangeset++
|
||
|
||
subscribers.forEach { it.onCellChanges(x, y, cell) }
|
||
}
|
||
|
||
protected inline fun forEachNeighbour(block: (This) -> Unit) {
|
||
world.chunkMap[pos.left]?.let(block)
|
||
world.chunkMap[pos.right]?.let(block)
|
||
world.chunkMap[pos.top]?.let(block)
|
||
world.chunkMap[pos.bottom]?.let(block)
|
||
world.chunkMap[pos.topLeft]?.let(block)
|
||
world.chunkMap[pos.topRight]?.let(block)
|
||
world.chunkMap[pos.bottomLeft]?.let(block)
|
||
world.chunkMap[pos.bottomRight]?.let(block)
|
||
}
|
||
|
||
fun addListener(subscriber: IChunkListener): Boolean {
|
||
if (subscribers.add(subscriber)) {
|
||
entities.forEach { subscriber.onEntityAdded(it) }
|
||
return true
|
||
}
|
||
|
||
return false
|
||
}
|
||
|
||
fun removeListener(subscriber: IChunkListener): Boolean {
|
||
if (subscribers.remove(subscriber)) {
|
||
entities.forEach { subscriber.onEntityRemoved(it) }
|
||
return true
|
||
}
|
||
|
||
return false
|
||
}
|
||
|
||
fun addEntity(entity: AbstractEntity) {
|
||
world.lock.withLock {
|
||
if (!entities.add(entity))
|
||
throw IllegalArgumentException("Already having having entity $entity")
|
||
|
||
if (entity is TileEntity)
|
||
tileEntities.add(entity)
|
||
|
||
if (entity is DynamicEntity)
|
||
dynamicEntities.add(entity)
|
||
|
||
changeset++
|
||
subscribers.forEach { it.onEntityAdded(entity) }
|
||
}
|
||
}
|
||
|
||
fun transferEntity(entity: AbstractEntity, otherChunk: Chunk<*, *>) {
|
||
world.lock.withLock {
|
||
if (otherChunk == this)
|
||
throw IllegalArgumentException("what?")
|
||
|
||
if (world != otherChunk.world)
|
||
throw IllegalArgumentException("Chunks belong to different worlds: this: $this / other: $otherChunk")
|
||
|
||
changeset++
|
||
otherChunk.changeset++
|
||
|
||
entities.add(entity)
|
||
otherChunk.entities.remove(entity)
|
||
|
||
if (entity is TileEntity) {
|
||
tileEntities.add(entity)
|
||
otherChunk.tileEntities.remove(entity)
|
||
}
|
||
|
||
if (entity is DynamicEntity) {
|
||
dynamicEntities.add(entity)
|
||
otherChunk.dynamicEntities.remove(entity)
|
||
}
|
||
|
||
otherChunk.subscribers.forEach { it.onEntityRemoved(entity) }
|
||
subscribers.forEach { it.onEntityAdded(entity) }
|
||
}
|
||
}
|
||
|
||
fun removeEntity(entity: AbstractEntity) {
|
||
world.lock.withLock {
|
||
if (!entities.remove(entity))
|
||
throw IllegalArgumentException("Already not having entity $entity")
|
||
|
||
if (entity is TileEntity)
|
||
tileEntities.remove(entity)
|
||
|
||
if (entity is DynamicEntity)
|
||
dynamicEntities.remove(entity)
|
||
|
||
changeset++
|
||
subscribers.forEach { it.onEntityRemoved(entity) }
|
||
}
|
||
}
|
||
|
||
override fun toString(): String {
|
||
return "${this::class.simpleName}(pos=$pos, entityCount=${entities.size}, world=$world)"
|
||
}
|
||
|
||
open fun remove() {
|
||
world.lock.withLock {
|
||
for (ent in ObjectArrayList(entities)) {
|
||
ent.chunk = null
|
||
}
|
||
}
|
||
}
|
||
|
||
open fun think() {
|
||
|
||
}
|
||
|
||
companion object {
|
||
private val aabbBase = AABB(
|
||
Vector2d.ZERO,
|
||
Vector2d(CHUNK_SIZE.toDouble(), CHUNK_SIZE.toDouble()),
|
||
)
|
||
}
|
||
}
|