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, This : Chunk>( 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() val dynamicEntities = ReferenceOpenHashSet() val tileEntities = ReferenceOpenHashSet() protected val subscribers = ObjectArraySet() // 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 { 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) { 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()), ) } }