Immutable chunk cells

This commit is contained in:
DBotThePony 2023-10-19 00:03:38 +07:00
parent ba0d792e83
commit 93deb62d4b
Signed by: DBot
GPG Key ID: DCC23B5715498507
25 changed files with 423 additions and 461 deletions

View File

@ -1,8 +1,6 @@
package ru.dbotthepony.kstarbound package ru.dbotthepony.kstarbound
import com.google.common.collect.ImmutableList
import com.google.gson.reflect.TypeToken
import org.apache.logging.log4j.LogManager import org.apache.logging.log4j.LogManager
import org.lwjgl.Version import org.lwjgl.Version
import org.lwjgl.glfw.GLFW.glfwSetWindowShouldClose import org.lwjgl.glfw.GLFW.glfwSetWindowShouldClose
@ -13,14 +11,13 @@ import ru.dbotthepony.kstarbound.player.Avatar
import ru.dbotthepony.kstarbound.player.QuestDescriptor import ru.dbotthepony.kstarbound.player.QuestDescriptor
import ru.dbotthepony.kstarbound.player.QuestInstance import ru.dbotthepony.kstarbound.player.QuestInstance
import ru.dbotthepony.kstarbound.util.JVMTimeSource import ru.dbotthepony.kstarbound.util.JVMTimeSource
import ru.dbotthepony.kstarbound.world.api.IChunkCell import ru.dbotthepony.kstarbound.world.api.AbstractCell
import ru.dbotthepony.kstarbound.world.entities.ItemEntity import ru.dbotthepony.kstarbound.world.entities.ItemEntity
import ru.dbotthepony.kstarbound.io.json.VersionedJson import ru.dbotthepony.kstarbound.io.json.VersionedJson
import ru.dbotthepony.kstarbound.io.readVarInt import ru.dbotthepony.kstarbound.io.readVarInt
import ru.dbotthepony.kstarbound.util.AssetPathStack import ru.dbotthepony.kstarbound.util.AssetPathStack
import ru.dbotthepony.kstarbound.world.api.MutableCell
import ru.dbotthepony.kstarbound.world.entities.WorldObject import ru.dbotthepony.kstarbound.world.entities.WorldObject
import ru.dbotthepony.kstarbound.world.physics.Poly
import ru.dbotthepony.kvector.util2d.AABB
import ru.dbotthepony.kvector.vector.Vector2d import ru.dbotthepony.kvector.vector.Vector2d
import java.io.BufferedInputStream import java.io.BufferedInputStream
import java.io.ByteArrayInputStream import java.io.ByteArrayInputStream
@ -30,7 +27,6 @@ import java.util.*
import java.util.concurrent.TimeUnit import java.util.concurrent.TimeUnit
import java.util.zip.Inflater import java.util.zip.Inflater
import java.util.zip.InflaterInputStream import java.util.zip.InflaterInputStream
import kotlin.collections.ArrayList
private val LOGGER = LogManager.getLogger() private val LOGGER = LogManager.getLogger()
@ -82,13 +78,12 @@ fun main() {
var reader = DataInputStream(BufferedInputStream(InflaterInputStream(ByteArrayInputStream(data), Inflater()))) var reader = DataInputStream(BufferedInputStream(InflaterInputStream(ByteArrayInputStream(data), Inflater())))
reader.skipBytes(3) reader.skipBytes(3)
for (y in 0 .. 31) { val chunk = client.world!!.chunkMap.compute(chunkX - 2, chunkY)
for (x in 0 .. 31) {
val cell = client.world!!.getCellDirect(chunkX * 32 + x, chunkY * 32 + y) if (chunk != null) {
if (cell == null) { for (y in 0 .. 31) {
IChunkCell.skip(reader) for (x in 0 .. 31) {
} else { check(chunk.setCell(x, y, MutableCell().read(reader)))
cell.read(reader)
} }
} }
} }

View File

@ -54,7 +54,7 @@ import ru.dbotthepony.kstarbound.util.forEachValid
import ru.dbotthepony.kstarbound.util.formatBytesShort import ru.dbotthepony.kstarbound.util.formatBytesShort
import ru.dbotthepony.kstarbound.world.LightCalculator import ru.dbotthepony.kstarbound.world.LightCalculator
import ru.dbotthepony.kstarbound.world.api.ICellAccess import ru.dbotthepony.kstarbound.world.api.ICellAccess
import ru.dbotthepony.kstarbound.world.api.IChunkCell import ru.dbotthepony.kstarbound.world.api.AbstractCell
import ru.dbotthepony.kvector.api.IStruct4f import ru.dbotthepony.kvector.api.IStruct4f
import ru.dbotthepony.kvector.arrays.Matrix3f import ru.dbotthepony.kvector.arrays.Matrix3f
import ru.dbotthepony.kvector.arrays.Matrix3fStack import ru.dbotthepony.kvector.arrays.Matrix3fStack
@ -662,12 +662,16 @@ class StarboundClient : Closeable {
val settings = ClientSettings() val settings = ClientSettings()
val viewportCells: ICellAccess = object : ICellAccess { val viewportCells: ICellAccess = object : ICellAccess {
override fun getCell(x: Int, y: Int): IChunkCell? { override fun getCell(x: Int, y: Int): AbstractCell {
return world?.getCell(x + viewportCellX, y + viewportCellY) return world!!.getCell(x + viewportCellX, y + viewportCellY)
} }
override fun getCellDirect(x: Int, y: Int): IChunkCell? { override fun getCellDirect(x: Int, y: Int): AbstractCell {
return world?.getCellDirect(x + viewportCellX, y + viewportCellY) return world!!.getCellDirect(x + viewportCellX, y + viewportCellY)
}
override fun setCell(x: Int, y: Int, cell: AbstractCell): Boolean {
return world!!.setCell(x + viewportCellX, y + viewportCellY, cell)
} }
} }

View File

@ -2,7 +2,7 @@ package ru.dbotthepony.kstarbound.client.render
import com.google.common.collect.ImmutableMap import com.google.common.collect.ImmutableMap
import org.apache.logging.log4j.LogManager import org.apache.logging.log4j.LogManager
import ru.dbotthepony.kstarbound.world.api.ITileState import ru.dbotthepony.kstarbound.world.api.AbstractTileState
import ru.dbotthepony.kstarbound.world.api.TileColor import ru.dbotthepony.kstarbound.world.api.TileColor
enum class RenderLayer { enum class RenderLayer {
@ -69,7 +69,7 @@ enum class RenderLayer {
} }
} }
fun tileLayer(isBackground: Boolean, isModifier: Boolean, tile: ITileState): Point { fun tileLayer(isBackground: Boolean, isModifier: Boolean, tile: AbstractTileState): Point {
if (isModifier) { if (isModifier) {
return tileLayer(isBackground, true, tile.modifier?.renderParameters?.zLevel ?: 0L, tile.modifier?.modId?.toLong() ?: 0L, tile.modifierHueShift) return tileLayer(isBackground, true, tile.modifier?.renderParameters?.zLevel ?: 0L, tile.modifier?.modId?.toLong() ?: 0L, tile.modifierHueShift)
} else { } else {

View File

@ -13,7 +13,7 @@ import ru.dbotthepony.kstarbound.client.gl.shader.UberShader
import ru.dbotthepony.kstarbound.client.gl.vertex.* import ru.dbotthepony.kstarbound.client.gl.vertex.*
import ru.dbotthepony.kstarbound.defs.tile.* import ru.dbotthepony.kstarbound.defs.tile.*
import ru.dbotthepony.kstarbound.world.api.ITileAccess import ru.dbotthepony.kstarbound.world.api.ITileAccess
import ru.dbotthepony.kstarbound.world.api.ITileState import ru.dbotthepony.kstarbound.world.api.AbstractTileState
import ru.dbotthepony.kstarbound.world.api.TileColor import ru.dbotthepony.kstarbound.world.api.TileColor
import ru.dbotthepony.kvector.vector.RGBAColor import ru.dbotthepony.kvector.vector.RGBAColor
import ru.dbotthepony.kvector.vector.Vector2i import ru.dbotthepony.kvector.vector.Vector2i
@ -94,13 +94,13 @@ class TileRenderers(val client: StarboundClient) {
} }
private class TileEqualityTester(val definition: TileDefinition) : EqualityRuleTester { private class TileEqualityTester(val definition: TileDefinition) : EqualityRuleTester {
override fun test(thisTile: ITileState?, otherTile: ITileState?): Boolean { override fun test(thisTile: AbstractTileState?, otherTile: AbstractTileState?): Boolean {
return otherTile?.material == definition && thisTile?.hueShift == otherTile.hueShift return otherTile?.material == definition && thisTile?.hueShift == otherTile.hueShift
} }
} }
private class ModifierEqualityTester(val definition: MaterialModifier) : EqualityRuleTester { private class ModifierEqualityTester(val definition: MaterialModifier) : EqualityRuleTester {
override fun test(thisTile: ITileState?, otherTile: ITileState?): Boolean { override fun test(thisTile: AbstractTileState?, otherTile: AbstractTileState?): Boolean {
return otherTile?.modifier == definition return otherTile?.modifier == definition
} }
} }
@ -125,7 +125,7 @@ class TileRenderer(val renderers: TileRenderers, val def: IRenderableTile) {
val bakedBackgroundProgramState = texture?.let { renderers.background(it) } val bakedBackgroundProgramState = texture?.let { renderers.background(it) }
// private var notifiedDepth = false // private var notifiedDepth = false
private fun tesselateAt(self: ITileState, piece: RenderPiece, getter: ITileAccess, builder: VertexBuilder, pos: Vector2i, offset: Vector2i = Vector2i.ZERO, isModifier: Boolean) { private fun tesselateAt(self: AbstractTileState, piece: RenderPiece, getter: ITileAccess, builder: VertexBuilder, pos: Vector2i, offset: Vector2i = Vector2i.ZERO, isModifier: Boolean) {
val fx = pos.x.toFloat() val fx = pos.x.toFloat()
val fy = pos.y.toFloat() val fy = pos.y.toFloat()
@ -173,7 +173,7 @@ class TileRenderer(val renderers: TileRenderers, val def: IRenderableTile) {
} }
private fun tesselatePiece( private fun tesselatePiece(
self: ITileState, self: AbstractTileState,
matchPiece: RenderMatch, matchPiece: RenderMatch,
getter: ITileAccess, getter: ITileAccess,
meshBuilder: LayeredRenderer, meshBuilder: LayeredRenderer,
@ -227,7 +227,7 @@ class TileRenderer(val renderers: TileRenderers, val def: IRenderableTile) {
* *
* Тесселирует тайлы в нужный VertexBuilder с масштабом согласно константе [PIXELS_IN_STARBOUND_UNITf] * Тесселирует тайлы в нужный VertexBuilder с масштабом согласно константе [PIXELS_IN_STARBOUND_UNITf]
*/ */
fun tesselate(self: ITileState, getter: ITileAccess, meshBuilder: LayeredRenderer, pos: Vector2i, isBackground: Boolean = false, isModifier: Boolean = false) { fun tesselate(self: AbstractTileState, getter: ITileAccess, meshBuilder: LayeredRenderer, pos: Vector2i, isBackground: Boolean = false, isModifier: Boolean = false) {
if (texture == null) return if (texture == null) return
// если у нас нет renderTemplate // если у нас нет renderTemplate

View File

@ -2,28 +2,29 @@ package ru.dbotthepony.kstarbound.client.world
import ru.dbotthepony.kstarbound.world.Chunk import ru.dbotthepony.kstarbound.world.Chunk
import ru.dbotthepony.kstarbound.world.ChunkPos import ru.dbotthepony.kstarbound.world.ChunkPos
import ru.dbotthepony.kstarbound.world.api.ImmutableCell
class ClientChunk(world: ClientWorld, pos: ChunkPos) : Chunk<ClientWorld, ClientChunk>(world, pos){ class ClientChunk(world: ClientWorld, pos: ChunkPos) : Chunk<ClientWorld, ClientChunk>(world, pos){
override fun foregroundChanges(cell: Cell) { override fun foregroundChanges(x: Int, y: Int, cell: ImmutableCell) {
super.foregroundChanges(cell) super.foregroundChanges(x, y, cell)
world.forEachRenderRegion(cell) { world.forEachRenderRegion(pos.tile(x, y)) {
it.foreground.isDirty = true it.foreground.isDirty = true
} }
} }
override fun backgroundChanges(cell: Cell) { override fun backgroundChanges(x: Int, y: Int, cell: ImmutableCell) {
super.backgroundChanges(cell) super.backgroundChanges(x, y, cell)
world.forEachRenderRegion(cell) { world.forEachRenderRegion(pos.tile(x, y)) {
it.background.isDirty = true it.background.isDirty = true
} }
} }
override fun liquidChanges(cell: Cell) { override fun liquidChanges(x: Int, y: Int, cell: ImmutableCell) {
super.liquidChanges(cell) super.liquidChanges(x, y, cell)
world.forEachRenderRegion(cell) { world.forEachRenderRegion(pos.tile(x, y)) {
it.liquidIsDirty = true it.liquidIsDirty = true
} }
} }

View File

@ -8,7 +8,7 @@ import ru.dbotthepony.kstarbound.defs.image.Image
import ru.dbotthepony.kstarbound.io.json.builder.JsonFactory import ru.dbotthepony.kstarbound.io.json.builder.JsonFactory
import ru.dbotthepony.kstarbound.util.WriteOnce import ru.dbotthepony.kstarbound.util.WriteOnce
import ru.dbotthepony.kstarbound.world.api.ITileAccess import ru.dbotthepony.kstarbound.world.api.ITileAccess
import ru.dbotthepony.kstarbound.world.api.ITileState import ru.dbotthepony.kstarbound.world.api.AbstractTileState
import ru.dbotthepony.kvector.vector.Vector2i import ru.dbotthepony.kvector.vector.Vector2i
@JsonFactory @JsonFactory
@ -21,7 +21,7 @@ data class RenderPiece(
) )
fun interface EqualityRuleTester { fun interface EqualityRuleTester {
fun test(thisTile: ITileState?, otherTile: ITileState?): Boolean fun test(thisTile: AbstractTileState?, otherTile: AbstractTileState?): Boolean
} }
@JsonFactory @JsonFactory

View File

@ -1,16 +1,10 @@
package ru.dbotthepony.kstarbound.world package ru.dbotthepony.kstarbound.world
import it.unimi.dsi.fastutil.objects.ReferenceOpenHashSet import it.unimi.dsi.fastutil.objects.ReferenceOpenHashSet
import ru.dbotthepony.kstarbound.defs.tile.BuiltinMetaMaterials import ru.dbotthepony.kstarbound.world.api.AbstractCell
import ru.dbotthepony.kstarbound.defs.tile.LiquidDefinition
import ru.dbotthepony.kstarbound.defs.tile.MaterialModifier
import ru.dbotthepony.kstarbound.defs.tile.TileDefinition
import ru.dbotthepony.kstarbound.world.api.ICellAccess import ru.dbotthepony.kstarbound.world.api.ICellAccess
import ru.dbotthepony.kstarbound.world.api.IChunkCell import ru.dbotthepony.kstarbound.world.api.ImmutableCell
import ru.dbotthepony.kstarbound.world.api.ILiquidState
import ru.dbotthepony.kstarbound.world.api.ITileState
import ru.dbotthepony.kstarbound.world.api.OffsetCellAccess import ru.dbotthepony.kstarbound.world.api.OffsetCellAccess
import ru.dbotthepony.kstarbound.world.api.TileColor
import ru.dbotthepony.kstarbound.world.api.TileView import ru.dbotthepony.kstarbound.world.api.TileView
import ru.dbotthepony.kstarbound.world.entities.Entity import ru.dbotthepony.kstarbound.world.entities.Entity
import ru.dbotthepony.kvector.arrays.Object2DArray import ru.dbotthepony.kvector.arrays.Object2DArray
@ -33,10 +27,7 @@ abstract class Chunk<WorldType : World<WorldType, This>, This : Chunk<WorldType,
val pos: ChunkPos, val pos: ChunkPos,
) : ICellAccess { ) : ICellAccess {
var changeset = 0 var changeset = 0
private set(value) { private set
field = value
promote()
}
var tileChangeset = 0 var tileChangeset = 0
private set private set
@ -50,32 +41,49 @@ abstract class Chunk<WorldType : World<WorldType, This>, This : Chunk<WorldType,
var backgroundChangeset = 0 var backgroundChangeset = 0
private set private set
private var isEmpty = true
fun promote() {
if (isEmpty) {
isEmpty = false
world.chunkMap.promote(this as This)
}
}
protected val cells = lazy { protected val cells = lazy {
Object2DArray.nulls<Cell>(CHUNK_SIZE, CHUNK_SIZE) Object2DArray(CHUNK_SIZE, CHUNK_SIZE, AbstractCell.NULL)
} }
override fun getCell(x: Int, y: Int): IChunkCell { override fun getCell(x: Int, y: Int): AbstractCell {
var get = cells.value[x, y] if (!cells.isInitialized())
return AbstractCell.NULL
if (get == null) { return cells.value[x, y]
get = Cell(x, y) }
cells.value[x, y] = get
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 get return true
}
override fun getCellDirect(x: Int, y: Int): IChunkCell {
return getCell(x, y)
} }
// local cells' tile access // local cells' tile access
@ -89,24 +97,24 @@ abstract class Chunk<WorldType : World<WorldType, This>, This : Chunk<WorldType,
val aabb = aabbBase + Vector2d(pos.x * CHUNK_SIZE.toDouble(), pos.y * CHUNK_SIZE.toDouble()) val aabb = aabbBase + Vector2d(pos.x * CHUNK_SIZE.toDouble(), pos.y * CHUNK_SIZE.toDouble())
protected open fun foregroundChanges(cell: Cell) { protected open fun foregroundChanges(x: Int, y: Int, cell: ImmutableCell) {
cellChanges(cell) cellChanges(x, y, cell)
tileChangeset++ tileChangeset++
foregroundChangeset++ foregroundChangeset++
} }
protected open fun backgroundChanges(cell: Cell) { protected open fun backgroundChanges(x: Int, y: Int, cell: ImmutableCell) {
cellChanges(cell) cellChanges(x, y, cell)
tileChangeset++ tileChangeset++
backgroundChangeset++ backgroundChangeset++
} }
protected open fun liquidChanges(cell: Cell) { protected open fun liquidChanges(x: Int, y: Int, cell: ImmutableCell) {
cellChanges(cell) cellChanges(x, y, cell)
liquidChangeset++ liquidChangeset++
} }
protected open fun cellChanges(cell: Cell) { protected open fun cellChanges(x: Int, y: Int, cell: ImmutableCell) {
changeset++ changeset++
cellChangeset++ cellChangeset++
} }
@ -122,130 +130,6 @@ abstract class Chunk<WorldType : World<WorldType, This>, This : Chunk<WorldType,
world.chunkMap[pos.bottomRight]?.let(block) world.chunkMap[pos.bottomRight]?.let(block)
} }
inner class Cell(x: Int, y: Int) : IChunkCell {
override val x: Int = x + pos.x * CHUNK_SIZE
override val y: Int = y + pos.y * CHUNK_SIZE
inner class Tile(private val foreground: Boolean) : ITileState {
private fun change() {
if (foreground) {
foregroundChanges(this@Cell)
} else {
backgroundChanges(this@Cell)
}
}
override var material: TileDefinition = BuiltinMetaMaterials.NULL
set(value) {
if (field !== value) {
field = value
change()
}
}
override var modifier: MaterialModifier? = null
set(value) {
if (field !== value) {
field = value
change()
}
}
override var color: TileColor = TileColor.DEFAULT
set(value) {
if (field != value) {
field = value
change()
}
}
override var hueShift: Float = 0f
set(value) {
if (field != value) {
field = value.coerceIn(0f, 360f)
change()
}
}
override var modifierHueShift: Float = 0f
set(value) {
if (field != value) {
field = value.coerceIn(0f, 360f)
change()
}
}
}
inner class Liquid : ILiquidState {
override var def: LiquidDefinition? = null
set(value) {
if (field !== value) {
field = value
liquidChanges(this@Cell)
}
}
override var level: Float = 0f
set(value) {
if (field != value) {
field = value
liquidChanges(this@Cell)
}
}
override var pressure: Float = 0f
set(value) {
if (field != value) {
field = value
liquidChanges(this@Cell)
}
}
override var isInfinite: Boolean = false
set(value) {
if (field != value) {
field = value
liquidChanges(this@Cell)
}
}
}
override val foreground = Tile(true)
override val background = Tile(false)
override val liquid = Liquid()
override var dungeonId: Int = 0
set(value) {
if (field != value) {
field = value
cellChanges(this)
}
}
override var biome: Int = 0
set(value) {
if (field != value) {
field = value
cellChanges(this)
}
}
override var envBiome: Int = 0
set(value) {
if (field != value) {
field = value
cellChanges(this)
}
}
override var isIndestructible: Boolean = false
set(value) {
if (field != value) {
field = value
cellChanges(this)
}
}
}
override fun randomLongFor(x: Int, y: Int): Long { override fun randomLongFor(x: Int, y: Int): Long {
return world.randomLongFor(x or pos.x shl CHUNK_SIZE_BITS, y or pos.y shl CHUNK_SIZE_BITS) return world.randomLongFor(x or pos.x shl CHUNK_SIZE_BITS, y or pos.y shl CHUNK_SIZE_BITS)
} }

View File

@ -33,6 +33,10 @@ data class ChunkPos(val x: Int, val y: Int) : IStruct2i, Comparable<ChunkPos> {
val tileY = y shl CHUNK_SIZE_BITS val tileY = y shl CHUNK_SIZE_BITS
val tile = Vector2i(tileX, tileY) val tile = Vector2i(tileX, tileY)
fun tile(x: Int, y: Int): Vector2i {
return Vector2i(tileX + x, tileY + y)
}
val top: ChunkPos get() { val top: ChunkPos get() {
return ChunkPos(x, y + 1) return ChunkPos(x, y + 1)
} }

View File

@ -1,9 +1,8 @@
package ru.dbotthepony.kstarbound.world package ru.dbotthepony.kstarbound.world
import ru.dbotthepony.kstarbound.world.METRES_IN_STARBOUND_UNIT
import ru.dbotthepony.kstarbound.math.roundTowardsNegativeInfinity import ru.dbotthepony.kstarbound.math.roundTowardsNegativeInfinity
import ru.dbotthepony.kstarbound.world.api.ICellAccess import ru.dbotthepony.kstarbound.world.api.ICellAccess
import ru.dbotthepony.kstarbound.world.api.IChunkCell import ru.dbotthepony.kstarbound.world.api.AbstractCell
import ru.dbotthepony.kvector.vector.Vector2d import ru.dbotthepony.kvector.vector.Vector2d
import ru.dbotthepony.kvector.vector.Vector2i import ru.dbotthepony.kvector.vector.Vector2i
import kotlin.collections.ArrayList import kotlin.collections.ArrayList
@ -20,7 +19,7 @@ data class RayCastResult(
) { ) {
constructor(startPos: Vector2d, direction: Vector2d) : this(listOf(), null, 0.0, startPos, startPos, direction) constructor(startPos: Vector2d, direction: Vector2d) : this(listOf(), null, 0.0, startPos, startPos, direction)
data class HitCell(val pos: Vector2i, val normal: Direction, val borderCross: Vector2d, val cell: IChunkCell) data class HitCell(val pos: Vector2i, val normal: Direction, val borderCross: Vector2d, val cell: AbstractCell)
} }
enum class RayFilterResult(val hit: Boolean, val write: Boolean) { enum class RayFilterResult(val hit: Boolean, val write: Boolean) {
@ -44,7 +43,7 @@ fun interface TileRayFilter {
/** /**
* [x] and [y] are wrapped around positions * [x] and [y] are wrapped around positions
*/ */
fun test(cell: IChunkCell, fraction: Double, x: Int, y: Int, normal: Direction, borderX: Double, borderY: Double): RayFilterResult fun test(cell: AbstractCell, fraction: Double, x: Int, y: Int, normal: Direction, borderX: Double, borderY: Double): RayFilterResult
} }
val NeverFilter = TileRayFilter { state, fraction, x, y, normal, borderX, borderY -> RayFilterResult.CONTINUE } val NeverFilter = TileRayFilter { state, fraction, x, y, normal, borderX, borderY -> RayFilterResult.CONTINUE }

View File

@ -10,12 +10,11 @@ import ru.dbotthepony.kstarbound.math.*
import ru.dbotthepony.kstarbound.util.MailboxExecutorService import ru.dbotthepony.kstarbound.util.MailboxExecutorService
import ru.dbotthepony.kstarbound.util.ParallelPerform import ru.dbotthepony.kstarbound.util.ParallelPerform
import ru.dbotthepony.kstarbound.world.api.ICellAccess import ru.dbotthepony.kstarbound.world.api.ICellAccess
import ru.dbotthepony.kstarbound.world.api.IChunkCell import ru.dbotthepony.kstarbound.world.api.AbstractCell
import ru.dbotthepony.kstarbound.world.api.TileView import ru.dbotthepony.kstarbound.world.api.TileView
import ru.dbotthepony.kstarbound.world.entities.Entity import ru.dbotthepony.kstarbound.world.entities.Entity
import ru.dbotthepony.kstarbound.world.entities.WorldObject import ru.dbotthepony.kstarbound.world.entities.WorldObject
import ru.dbotthepony.kstarbound.world.physics.CollisionPoly import ru.dbotthepony.kstarbound.world.physics.CollisionPoly
import ru.dbotthepony.kstarbound.world.physics.CollisionType
import ru.dbotthepony.kstarbound.world.physics.Poly import ru.dbotthepony.kstarbound.world.physics.Poly
import ru.dbotthepony.kvector.api.IStruct2d import ru.dbotthepony.kvector.api.IStruct2d
import ru.dbotthepony.kvector.api.IStruct2i import ru.dbotthepony.kvector.api.IStruct2i
@ -23,8 +22,6 @@ import ru.dbotthepony.kvector.arrays.Object2DArray
import ru.dbotthepony.kvector.util2d.AABB import ru.dbotthepony.kvector.util2d.AABB
import ru.dbotthepony.kvector.vector.Vector2d import ru.dbotthepony.kvector.vector.Vector2d
import ru.dbotthepony.kvector.vector.Vector2i import ru.dbotthepony.kvector.vector.Vector2i
import java.lang.ref.ReferenceQueue
import java.lang.ref.WeakReference
import java.util.concurrent.ForkJoinPool import java.util.concurrent.ForkJoinPool
import java.util.concurrent.locks.ReentrantLock import java.util.concurrent.locks.ReentrantLock
import java.util.random.RandomGenerator import java.util.random.RandomGenerator
@ -54,33 +51,31 @@ abstract class World<This : World<This, ChunkType>, ChunkType : Chunk<This, Chun
final override fun randomLongFor(x: Int, y: Int) = super.randomLongFor(x, y) xor seed final override fun randomLongFor(x: Int, y: Int) = super.randomLongFor(x, y) xor seed
override fun getCellDirect(x: Int, y: Int): IChunkCell? { override fun getCellDirect(x: Int, y: Int): AbstractCell {
if (!this.x.inBoundsCell(x) || !this.y.inBoundsCell(y)) return null if (!this.x.inBoundsCell(x) || !this.y.inBoundsCell(y)) return AbstractCell.NULL
return getCell(x, y) return getCell(x, y)
} }
override fun getCell(x: Int, y: Int): IChunkCell? { override fun getCell(x: Int, y: Int): AbstractCell {
return chunkMap.getCell(x, y) return chunkMap.getCell(x, y)
} }
override fun setCell(x: Int, y: Int, cell: AbstractCell): Boolean {
return chunkMap.setCell(x, y, cell)
}
abstract inner class ChunkMap { abstract inner class ChunkMap {
abstract operator fun get(x: Int, y: Int): ChunkType? abstract operator fun get(x: Int, y: Int): ChunkType?
abstract fun compute(x: Int, y: Int): ChunkType?
abstract fun promote(self: ChunkType)
abstract fun purge()
abstract fun remove(x: Int, y: Int) abstract fun remove(x: Int, y: Int)
abstract fun getCell(x: Int, y: Int): IChunkCell? abstract fun getCell(x: Int, y: Int): AbstractCell
abstract fun setCell(x: Int, y: Int, cell: AbstractCell): Boolean
protected val queue = ReferenceQueue<ChunkType>()
operator fun get(pos: ChunkPos) = get(pos.x, pos.y) operator fun get(pos: ChunkPos) = get(pos.x, pos.y)
protected inner class Ref(chunk: ChunkType) : WeakReference<ChunkType>(chunk, queue) {
val pos = chunk.pos
}
protected fun create(x: Int, y: Int): ChunkType { protected fun create(x: Int, y: Int): ChunkType {
purge()
val pos = ChunkPos(x, y) val pos = ChunkPos(x, y)
val chunk = chunkFactory(pos) val chunk = chunkFactory(pos)
val orphanedInThisChunk = ArrayList<Entity>() val orphanedInThisChunk = ArrayList<Entity>()
@ -103,68 +98,41 @@ abstract class World<This : World<This, ChunkType>, ChunkType : Chunk<This, Chun
// hash chunk map is around 30% slower than rectangular one // hash chunk map is around 30% slower than rectangular one
inner class HashChunkMap : ChunkMap() { inner class HashChunkMap : ChunkMap() {
private val map = Long2ObjectOpenHashMap<Any>() private val map = Long2ObjectOpenHashMap<ChunkType>()
override fun getCell(x: Int, y: Int): IChunkCell? { override fun getCell(x: Int, y: Int): AbstractCell {
if (!this@World.x.isValidCellIndex(x) || !this@World.y.isValidCellIndex(y)) return null if (!this@World.x.isValidCellIndex(x) || !this@World.y.isValidCellIndex(y)) return AbstractCell.NULL
val ix = this@World.x.cell(x) val ix = this@World.x.cell(x)
val iy = this@World.y.cell(y) val iy = this@World.y.cell(y)
return this[this@World.x.chunkFromCell(ix), this@World.y.chunkFromCell(iy)]?.getCell(ix and CHUNK_SIZE_MASK, iy and CHUNK_SIZE_MASK) return this[this@World.x.chunkFromCell(ix), this@World.y.chunkFromCell(iy)]?.getCell(ix and CHUNK_SIZE_MASK, iy and CHUNK_SIZE_MASK) ?: AbstractCell.NULL
} }
@Suppress("NAME_SHADOWING")
override fun get(x: Int, y: Int): ChunkType? { override fun get(x: Int, y: Int): ChunkType? {
if (!this@World.x.isValidChunkIndex(x) || !this@World.y.isValidChunkIndex(y)) return null if (!this@World.x.isValidChunkIndex(x) || !this@World.y.isValidChunkIndex(y)) return null
val x = this@World.x.chunk(x) val x = this@World.x.chunk(x)
val y = this@World.y.chunk(y) val y = this@World.y.chunk(y)
return map[ChunkPos.toLong(x, y)]?.let { return map[ChunkPos.toLong(x, y)]
if (it is World<*, *>.ChunkMap.Ref) {
it.get() as ChunkType?
} else {
it as ChunkType?
}
} ?: create(x, y).also { map[ChunkPos.toLong(x, y)] = Ref(it) }
} }
override fun purge() { override fun compute(x: Int, y: Int): ChunkType? {
var next = queue.poll() as World<*, *>.ChunkMap.Ref? if (!this@World.x.inBoundsChunk(x) || !this@World.y.inBoundsChunk(y)) return null
return map[ChunkPos.toLong(x, y)] ?: create(x, y).also { map[ChunkPos.toLong(x, y)] = it }
while (next != null) {
val k = ChunkPos.toLong(next.pos.x, next.pos.y)
val get = map[k]
if (get === next)
map.remove(k)
next = queue.poll() as World<*, *>.ChunkMap.Ref?
}
} }
override fun promote(self: ChunkType) { override fun setCell(x: Int, y: Int, cell: AbstractCell): Boolean {
val (x, y) = self.pos if (!this@World.x.isValidCellIndex(x) || !this@World.y.isValidCellIndex(y)) return false
val ref = map[ChunkPos.toLong(x, y)] val ix = this@World.x.cell(x)
val iy = this@World.y.cell(y)
if (ref !is World<*, *>.ChunkMap.Ref) { val cx = this@World.x.chunkFromCell(ix)
throw IllegalStateException("Tried to promote chunk from weak to strong storage at $x, $y; but there is $ref") val cy = this@World.y.chunkFromCell(iy)
} return (map[ChunkPos.toLong(cx, cy)] ?: create(cx, cy).also { map[ChunkPos.toLong(cx, cy)] = it }).setCell(ix and CHUNK_SIZE_MASK, iy and CHUNK_SIZE_MASK, cell)
if (!(ref as World<*, ChunkType>.ChunkMap.Ref).refersTo(self)) {
throw IllegalStateException("Tried to promote chunk from weak to strong storage at $x, $y; but it doesn't refer to valid object (self: $self, referent: ${ref.get()})")
}
map[ChunkPos.toLong(x, y)] = self
} }
override fun remove(x: Int, y: Int) { override fun remove(x: Int, y: Int) {
val x = this@World.x.chunk(x) map.remove(ChunkPos.toLong(this@World.x.chunk(x), this@World.y.chunk(y)))
val y = this@World.y.chunk(y)
val ref = map.remove(ChunkPos.toLong(x, y))
if (ref is World<*, *>.ChunkMap.Ref) {
ref.clear()
}
} }
} }
@ -172,64 +140,38 @@ abstract class World<This : World<This, ChunkType>, ChunkType : Chunk<This, Chun
val width = size!!.x val width = size!!.x
val height = size!!.y val height = size!!.y
private val map = Object2DArray.nulls<Any>(divideUp(width, CHUNK_SIZE), divideUp(height, CHUNK_SIZE)) private val map = Object2DArray.nulls<ChunkType>(divideUp(width, CHUNK_SIZE), divideUp(height, CHUNK_SIZE))
private fun getRaw(x: Int, y: Int): ChunkType { private fun getRaw(x: Int, y: Int): ChunkType {
return map[x, y]?.let { return map[x, y] ?: create(x, y).also { map[x, y] = it }
if (it is World<*, *>.ChunkMap.Ref) {
it.get() as ChunkType?
} else {
it as ChunkType?
}
} ?: create(x, y).also {
map[x, y] = Ref(it)
}
} }
override fun getCell(x: Int, y: Int): IChunkCell? { override fun compute(x: Int, y: Int): ChunkType? {
if (!this@World.x.isValidCellIndex(x) || !this@World.y.isValidCellIndex(y)) return null if (!this@World.x.inBoundsChunk(x) || !this@World.y.inBoundsChunk(y)) return null
return getRaw(x, y)
}
override fun getCell(x: Int, y: Int): AbstractCell {
if (!this@World.x.isValidCellIndex(x) || !this@World.y.isValidCellIndex(y)) return AbstractCell.NULL
val ix = this@World.x.cell(x) val ix = this@World.x.cell(x)
val iy = this@World.y.cell(y) val iy = this@World.y.cell(y)
return getRaw(ix ushr CHUNK_SIZE_BITS, iy ushr CHUNK_SIZE_BITS).getCell(ix and CHUNK_SIZE_MASK, iy and CHUNK_SIZE_MASK) return getRaw(ix ushr CHUNK_SIZE_BITS, iy ushr CHUNK_SIZE_BITS).getCell(ix and CHUNK_SIZE_MASK, iy and CHUNK_SIZE_MASK)
} }
override fun setCell(x: Int, y: Int, cell: AbstractCell): Boolean {
if (!this@World.x.isValidCellIndex(x) || !this@World.y.isValidCellIndex(y)) return false
val ix = this@World.x.cell(x)
val iy = this@World.y.cell(y)
return getRaw(ix ushr CHUNK_SIZE_BITS, iy ushr CHUNK_SIZE_BITS).setCell(ix and CHUNK_SIZE_MASK, iy and CHUNK_SIZE_MASK, cell)
}
override fun get(x: Int, y: Int): ChunkType? { override fun get(x: Int, y: Int): ChunkType? {
if (!this@World.x.isValidChunkIndex(x) || !this@World.y.isValidChunkIndex(y)) return null if (!this@World.x.isValidChunkIndex(x) || !this@World.y.isValidChunkIndex(y)) return null
return getRaw(this@World.x.chunk(x), this@World.y.chunk(y)) return getRaw(this@World.x.chunk(x), this@World.y.chunk(y))
} }
override fun purge() {
while (queue.poll() != null) {}
}
override fun promote(self: ChunkType) {
val (x, y) = self.pos
val ref = map[x, y]
if (ref !is World<*, *>.ChunkMap.Ref) {
throw IllegalStateException("Tried to promote chunk from weak to strong storage at $x, $y; but there is $ref")
}
if (!(ref as World<*, ChunkType>.ChunkMap.Ref).refersTo(self)) {
throw IllegalStateException("Tried to promote chunk from weak to strong storage at $x, $y; but it doesn't refer to valid object (self: $self, referent: ${ref.get()})")
}
map[x, y] = self
}
override fun remove(x: Int, y: Int) { override fun remove(x: Int, y: Int) {
val x = this@World.x.chunk(x) map[this@World.x.chunk(x), this@World.y.chunk(y)] = null
val y = this@World.y.chunk(y)
val old = map[x, y]
if (old is World<*, *>.ChunkMap.Ref) {
old.clear()
}
map[x, y] = null
} }
} }

View File

@ -0,0 +1,29 @@
package ru.dbotthepony.kstarbound.world.api
import java.io.DataInputStream
sealed class AbstractCell {
abstract val foreground: AbstractTileState
abstract val background: AbstractTileState
abstract val liquid: AbstractLiquidState
abstract val dungeonId: Int
abstract val biome: Int
abstract val envBiome: Int
abstract val isIndestructible: Boolean
abstract fun immutable(): ImmutableCell
abstract fun mutable(): MutableCell
companion object {
fun skip(stream: DataInputStream) {
AbstractTileState.skip(stream)
AbstractTileState.skip(stream)
AbstractLiquidState.skip(stream)
stream.skipNBytes(1 + 2 + 1 + 1 + 1 + 1)
}
val EMPTY = ImmutableCell(AbstractTileState.EMPTY, AbstractTileState.EMPTY, AbstractLiquidState.EMPTY, 0, 0, 0, false)
val NULL = ImmutableCell(AbstractTileState.NULL, AbstractTileState.NULL, AbstractLiquidState.EMPTY, 0, 0, 0, false)
}
}

View File

@ -0,0 +1,23 @@
package ru.dbotthepony.kstarbound.world.api
import ru.dbotthepony.kstarbound.Registries
import ru.dbotthepony.kstarbound.defs.tile.LiquidDefinition
import java.io.DataInputStream
sealed class AbstractLiquidState {
abstract val def: LiquidDefinition?
abstract val level: Float
abstract val pressure: Float
abstract val isInfinite: Boolean
abstract fun mutable(): MutableLiquidState
abstract fun immutable(): ImmutableLiquidState
companion object {
fun skip(stream: DataInputStream) {
stream.skipNBytes(1 + 4 + 4 + 1)
}
val EMPTY = ImmutableLiquidState()
}
}

View File

@ -0,0 +1,26 @@
package ru.dbotthepony.kstarbound.world.api
import ru.dbotthepony.kstarbound.defs.tile.BuiltinMetaMaterials
import ru.dbotthepony.kstarbound.defs.tile.MaterialModifier
import ru.dbotthepony.kstarbound.defs.tile.TileDefinition
import java.io.DataInputStream
sealed class AbstractTileState {
abstract val material: TileDefinition
abstract val modifier: MaterialModifier?
abstract val color: TileColor
abstract val hueShift: Float
abstract val modifierHueShift: Float
abstract fun immutable(): ImmutableTileState
abstract fun mutable(): MutableTileState
companion object {
fun skip(stream: DataInputStream) {
stream.skipNBytes(2 + 1 + 1 + 2 + 1)
}
val EMPTY = ImmutableTileState(BuiltinMetaMaterials.EMPTY)
val NULL = ImmutableTileState(BuiltinMetaMaterials.NULL)
}
}

View File

@ -5,19 +5,22 @@ import ru.dbotthepony.kvector.vector.Vector2i
interface ICellAccess { interface ICellAccess {
/** /**
* non-null - valid cell (maybe wrapped around) * with wrap-around
* null - invalid cell (outside world bounds)
*/ */
fun getCell(x: Int, y: Int): IChunkCell? fun getCell(x: Int, y: Int): AbstractCell
fun getCell(pos: IStruct2i) = getCell(pos.component1(), pos.component2()) fun getCell(pos: IStruct2i) = getCell(pos.component1(), pos.component2())
/** /**
* non-null - valid cell and not wrapped around * without wrap-around
* null - invalid cell (outside world bounds)
*/ */
fun getCellDirect(x: Int, y: Int): IChunkCell? fun getCellDirect(x: Int, y: Int): AbstractCell
fun getCellDirect(pos: IStruct2i) = getCellDirect(pos.component1(), pos.component2()) fun getCellDirect(pos: IStruct2i) = getCellDirect(pos.component1(), pos.component2())
/**
* whenever cell was set
*/
fun setCell(x: Int, y: Int, cell: AbstractCell): Boolean
/** /**
* Возвращает псевдослучайное Long для заданной позиции * Возвращает псевдослучайное Long для заданной позиции
* *
@ -45,19 +48,3 @@ interface ICellAccess {
fun randomDoubleFor(pos: Vector2i) = randomDoubleFor(pos.x, pos.y) fun randomDoubleFor(pos: Vector2i) = randomDoubleFor(pos.x, pos.y)
} }
class OffsetCellAccess(private val parent: ICellAccess, var x: Int, var y: Int) : ICellAccess {
constructor(parent: ICellAccess, offset: IStruct2i) : this(parent, offset.component1(), offset.component2())
override fun getCell(x: Int, y: Int): IChunkCell? {
return parent.getCell(x + this.x, y + this.y)
}
override fun getCellDirect(x: Int, y: Int): IChunkCell? {
return parent.getCellDirect(x + this.x, y + this.y)
}
override fun randomLongFor(x: Int, y: Int) = parent.randomLongFor(x + this.x, y + this.y)
override fun randomDoubleFor(x: Int, y: Int) = parent.randomDoubleFor(x + this.x, y + this.y)
override fun randomLongFor(pos: Vector2i) = parent.randomLongFor(pos.x + this.x, pos.y + this.y)
override fun randomDoubleFor(pos: Vector2i) = parent.randomDoubleFor(pos.x + this.x, pos.y + this.y)
}

View File

@ -1,66 +0,0 @@
package ru.dbotthepony.kstarbound.world.api
import ru.dbotthepony.kstarbound.RegistryObject
import ru.dbotthepony.kstarbound.defs.tile.LiquidDefinition
import ru.dbotthepony.kstarbound.defs.tile.MaterialModifier
import ru.dbotthepony.kstarbound.defs.tile.TileDefinition
import ru.dbotthepony.kstarbound.world.physics.Poly
import ru.dbotthepony.kvector.api.IStruct2i
import ru.dbotthepony.kvector.util2d.AABB
import ru.dbotthepony.kvector.vector.Vector2d
import java.io.DataInputStream
private val rect = Poly(listOf(Vector2d.ZERO, Vector2d(0.0, 1.0), Vector2d(1.0, 1.0), Vector2d(1.0, 0.0)))
interface IChunkCell : IStruct2i {
/**
* absolute (in world)
*/
val x: Int
/**
* absolute (in world)
*/
val y: Int
override fun component1(): Int {
return x
}
override fun component2(): Int {
return y
}
val foreground: ITileState
val background: ITileState
val liquid: ILiquidState
var dungeonId: Int
var biome: Int
var envBiome: Int
var isIndestructible: Boolean
fun read(stream: DataInputStream) {
foreground.read(stream)
background.read(stream)
liquid.read(stream)
stream.skipNBytes(1) // collisionMap
dungeonId = stream.readUnsignedShort()
biome = stream.readUnsignedByte()
envBiome = stream.readUnsignedByte()
isIndestructible = stream.readBoolean()
stream.skipNBytes(1) // unknown
}
companion object {
fun skip(stream: DataInputStream) {
ITileState.skip(stream)
ITileState.skip(stream)
ILiquidState.skip(stream)
stream.skipNBytes(1 + 2 + 1 + 1 + 1 + 1)
}
}
}

View File

@ -1,26 +0,0 @@
package ru.dbotthepony.kstarbound.world.api
import ru.dbotthepony.kstarbound.Registries
import ru.dbotthepony.kstarbound.RegistryObject
import ru.dbotthepony.kstarbound.defs.tile.LiquidDefinition
import java.io.DataInputStream
interface ILiquidState {
var def: LiquidDefinition?
var level: Float
var pressure: Float
var isInfinite: Boolean
fun read(stream: DataInputStream) {
def = Registries.liquid[stream.readUnsignedByte()]?.value
level = stream.readFloat()
pressure = stream.readFloat()
isInfinite = stream.readBoolean()
}
companion object {
fun skip(stream: DataInputStream) {
stream.skipNBytes(1 + 4 + 4 + 1)
}
}
}

View File

@ -5,30 +5,8 @@ import ru.dbotthepony.kvector.api.IStruct2i
// for getting tiles directly, avoiding manual layer specification // for getting tiles directly, avoiding manual layer specification
interface ITileAccess : ICellAccess { interface ITileAccess : ICellAccess {
// relative // relative
fun getTile(x: Int, y: Int): ITileState? fun getTile(x: Int, y: Int): AbstractTileState?
fun getTile(pos: IStruct2i) = getTile(pos.component1(), pos.component2()) fun getTile(pos: IStruct2i) = getTile(pos.component1(), pos.component2())
fun getTileDirect(x: Int, y: Int): ITileState? fun getTileDirect(x: Int, y: Int): AbstractTileState?
fun getTileDirect(pos: IStruct2i) = getTile(pos.component1(), pos.component2()) fun getTileDirect(pos: IStruct2i) = getTile(pos.component1(), pos.component2())
} }
sealed class TileView(parent: ICellAccess) : ITileAccess, ICellAccess by parent {
class Foreground(parent: ICellAccess) : TileView(parent) {
override fun getTile(x: Int, y: Int): ITileState? {
return getCell(x, y)?.foreground
}
override fun getTileDirect(x: Int, y: Int): ITileState? {
return getCellDirect(x, y)?.foreground
}
}
class Background(parent: ICellAccess) : TileView(parent) {
override fun getTile(x: Int, y: Int): ITileState? {
return getCell(x, y)?.background
}
override fun getTileDirect(x: Int, y: Int): ITileState? {
return getCellDirect(x, y)?.background
}
}
}

View File

@ -0,0 +1,20 @@
package ru.dbotthepony.kstarbound.world.api
data class ImmutableCell(
override val foreground: ImmutableTileState = AbstractTileState.EMPTY,
override val background: ImmutableTileState = AbstractTileState.EMPTY,
override val liquid: ImmutableLiquidState = AbstractLiquidState.EMPTY,
override val dungeonId: Int = 0,
override val biome: Int = 0,
override val envBiome: Int = 0,
override val isIndestructible: Boolean = false,
) : AbstractCell() {
override fun immutable(): ImmutableCell {
return this
}
override fun mutable(): MutableCell {
return MutableCell(foreground.mutable(), background.mutable(), liquid.mutable(), dungeonId, biome, envBiome, isIndestructible)
}
}

View File

@ -0,0 +1,18 @@
package ru.dbotthepony.kstarbound.world.api
import ru.dbotthepony.kstarbound.defs.tile.LiquidDefinition
data class ImmutableLiquidState(
override val def: LiquidDefinition? = null,
override val level: Float = 0f,
override val pressure: Float = 0f,
override val isInfinite: Boolean = false,
) : AbstractLiquidState() {
override fun mutable(): MutableLiquidState {
return MutableLiquidState(def, level, pressure, isInfinite)
}
override fun immutable(): ImmutableLiquidState {
return this
}
}

View File

@ -0,0 +1,21 @@
package ru.dbotthepony.kstarbound.world.api
import ru.dbotthepony.kstarbound.defs.tile.BuiltinMetaMaterials
import ru.dbotthepony.kstarbound.defs.tile.MaterialModifier
import ru.dbotthepony.kstarbound.defs.tile.TileDefinition
data class ImmutableTileState(
override var material: TileDefinition = BuiltinMetaMaterials.NULL,
override var modifier: MaterialModifier? = null,
override var color: TileColor = TileColor.DEFAULT,
override var hueShift: Float = 0f,
override var modifierHueShift: Float = 0f,
) : AbstractTileState() {
override fun immutable(): ImmutableTileState {
return this
}
override fun mutable(): MutableTileState {
return MutableTileState(material, modifier, color, hueShift, modifierHueShift)
}
}

View File

@ -0,0 +1,38 @@
package ru.dbotthepony.kstarbound.world.api
import java.io.DataInputStream
data class MutableCell(
override var foreground: MutableTileState = MutableTileState(),
override var background: MutableTileState = MutableTileState(),
override var liquid: MutableLiquidState = MutableLiquidState(),
override var dungeonId: Int = 0,
override var biome: Int = 0,
override var envBiome: Int = 0,
override var isIndestructible: Boolean = false,
) : AbstractCell() {
fun read(stream: DataInputStream): MutableCell {
foreground.read(stream)
background.read(stream)
liquid.read(stream)
stream.skipNBytes(1) // collisionMap
dungeonId = stream.readUnsignedShort()
biome = stream.readUnsignedByte()
envBiome = stream.readUnsignedByte()
isIndestructible = stream.readBoolean()
stream.skipNBytes(1) // unknown
return this
}
override fun immutable(): ImmutableCell {
return ImmutableCell(foreground.immutable(), background.immutable(), liquid.immutable(), dungeonId, biome, envBiome, isIndestructible)
}
override fun mutable(): MutableCell {
return this
}
}

View File

@ -0,0 +1,28 @@
package ru.dbotthepony.kstarbound.world.api
import ru.dbotthepony.kstarbound.Registries
import ru.dbotthepony.kstarbound.defs.tile.LiquidDefinition
import java.io.DataInputStream
data class MutableLiquidState(
override var def: LiquidDefinition? = null,
override var level: Float = 0f,
override var pressure: Float = 0f,
override var isInfinite: Boolean = false,
) : AbstractLiquidState() {
fun read(stream: DataInputStream): MutableLiquidState {
def = Registries.liquid[stream.readUnsignedByte()]?.value
level = stream.readFloat()
pressure = stream.readFloat()
isInfinite = stream.readBoolean()
return this
}
override fun mutable(): MutableLiquidState {
return this
}
override fun immutable(): ImmutableLiquidState {
return ImmutableLiquidState(def, level, pressure, isInfinite)
}
}

View File

@ -1,20 +1,30 @@
package ru.dbotthepony.kstarbound.world.api package ru.dbotthepony.kstarbound.world.api
import ru.dbotthepony.kstarbound.Registries import ru.dbotthepony.kstarbound.Registries
import ru.dbotthepony.kstarbound.RegistryObject
import ru.dbotthepony.kstarbound.defs.tile.BuiltinMetaMaterials import ru.dbotthepony.kstarbound.defs.tile.BuiltinMetaMaterials
import ru.dbotthepony.kstarbound.defs.tile.MaterialModifier import ru.dbotthepony.kstarbound.defs.tile.MaterialModifier
import ru.dbotthepony.kstarbound.defs.tile.TileDefinition import ru.dbotthepony.kstarbound.defs.tile.TileDefinition
import java.io.DataInputStream import java.io.DataInputStream
interface ITileState { data class MutableTileState(
var material: TileDefinition override var material: TileDefinition = BuiltinMetaMaterials.NULL,
var modifier: MaterialModifier? override var modifier: MaterialModifier? = null,
var color: TileColor override var color: TileColor = TileColor.DEFAULT,
var hueShift: Float override var hueShift: Float = 0f,
var modifierHueShift: Float override var modifierHueShift: Float = 0f,
) : AbstractTileState() {
override fun immutable(): ImmutableTileState {
val result = ImmutableTileState(material, modifier, color, hueShift, modifierHueShift)
if (result == NULL) return NULL
if (result == EMPTY) return EMPTY
return result
}
fun setHueShift(value: Int) { override fun mutable(): MutableTileState {
return this
}
fun setHueShift(value: Int): MutableTileState {
if (value < 0) { if (value < 0) {
hueShift = 0f hueShift = 0f
} else if (value >= 255) { } else if (value >= 255) {
@ -22,9 +32,11 @@ interface ITileState {
} else { } else {
hueShift = value / 255f * 360f hueShift = value / 255f * 360f
} }
return this
} }
fun setModHueShift(value: Int) { fun setModHueShift(value: Int): MutableTileState {
if (value < 0) { if (value < 0) {
modifierHueShift = 0f modifierHueShift = 0f
} else if (value >= 255) { } else if (value >= 255) {
@ -32,19 +44,16 @@ interface ITileState {
} else { } else {
modifierHueShift = value / 255f * 360f modifierHueShift = value / 255f * 360f
} }
return this
} }
fun read(stream: DataInputStream) { fun read(stream: DataInputStream): MutableTileState {
material = Registries.tiles[stream.readUnsignedShort()]?.value ?: BuiltinMetaMaterials.EMPTY material = Registries.tiles[stream.readUnsignedShort()]?.value ?: BuiltinMetaMaterials.EMPTY
setHueShift(stream.read()) setHueShift(stream.read())
color = TileColor.of(stream.read()) color = TileColor.of(stream.read())
modifier = Registries.tileModifiers[stream.readUnsignedShort()]?.value modifier = Registries.tileModifiers[stream.readUnsignedShort()]?.value
setModHueShift(stream.read()) setModHueShift(stream.read())
return this
} }
}
companion object {
fun skip(stream: DataInputStream) {
stream.skipNBytes(2 + 1 + 1 + 2 + 1)
}
}
}

View File

@ -0,0 +1,25 @@
package ru.dbotthepony.kstarbound.world.api
import ru.dbotthepony.kvector.api.IStruct2i
import ru.dbotthepony.kvector.vector.Vector2i
class OffsetCellAccess(private val parent: ICellAccess, var x: Int, var y: Int) : ICellAccess {
constructor(parent: ICellAccess, offset: IStruct2i) : this(parent, offset.component1(), offset.component2())
override fun getCell(x: Int, y: Int): AbstractCell {
return parent.getCell(x + this.x, y + this.y)
}
override fun getCellDirect(x: Int, y: Int): AbstractCell {
return parent.getCellDirect(x + this.x, y + this.y)
}
override fun setCell(x: Int, y: Int, cell: AbstractCell): Boolean {
return parent.setCell(x, y, cell)
}
override fun randomLongFor(x: Int, y: Int) = parent.randomLongFor(x + this.x, y + this.y)
override fun randomDoubleFor(x: Int, y: Int) = parent.randomDoubleFor(x + this.x, y + this.y)
override fun randomLongFor(pos: Vector2i) = parent.randomLongFor(pos.x + this.x, pos.y + this.y)
override fun randomDoubleFor(pos: Vector2i) = parent.randomDoubleFor(pos.x + this.x, pos.y + this.y)
}

View File

@ -0,0 +1,23 @@
package ru.dbotthepony.kstarbound.world.api
sealed class TileView(parent: ICellAccess) : ITileAccess, ICellAccess by parent {
class Foreground(parent: ICellAccess) : TileView(parent) {
override fun getTile(x: Int, y: Int): AbstractTileState? {
return getCell(x, y)?.foreground
}
override fun getTileDirect(x: Int, y: Int): AbstractTileState? {
return getCellDirect(x, y)?.foreground
}
}
class Background(parent: ICellAccess) : TileView(parent) {
override fun getTile(x: Int, y: Int): AbstractTileState? {
return getCell(x, y)?.background
}
override fun getTileDirect(x: Int, y: Int): AbstractTileState? {
return getCellDirect(x, y)?.background
}
}
}