TileModification packets
This commit is contained in:
parent
b3b51aefa5
commit
7cd0f5e173
@ -61,3 +61,4 @@ val color: TileColor = TileColor.DEFAULT
|
||||
* Implemented `isConnectable`, which was planned by original developers, but scrapped in process (defaults to `true`, by default only next meta-materials have it set to false: `empty`, `null` and `boundary`)
|
||||
* Used by object and plant anchoring code to determine valid placement
|
||||
* Used by world tile rendering code (render piece rule `Connects`)
|
||||
* And finally, used by `canPlaceMaterial` to determine whenever player can place blocks next to it (at least one such tile should be present for player to be able to place blocks next to it)
|
||||
|
@ -15,7 +15,7 @@ import java.io.DataInputStream
|
||||
import java.io.DataOutputStream
|
||||
|
||||
class ChunkCellsPacket(val pos: ChunkPos, val data: List<ImmutableCell>) : IClientPacket {
|
||||
constructor(stream: DataInputStream, isLegacy: Boolean) : this(stream.readChunkPos(), stream.readCollection { MutableCell().read(stream).immutable() })
|
||||
constructor(stream: DataInputStream, isLegacy: Boolean) : this(stream.readChunkPos(), stream.readCollection { MutableCell().readLegacy(stream).immutable() })
|
||||
constructor(chunk: Chunk<*, *>) : this(chunk.pos, ArrayList<ImmutableCell>(CHUNK_SIZE * CHUNK_SIZE).also {
|
||||
for (x in 0 until CHUNK_SIZE) {
|
||||
for (y in 0 until CHUNK_SIZE) {
|
||||
|
@ -260,7 +260,7 @@ class DungeonPart(data: JsonData) {
|
||||
|
||||
// Mark entities for removal, and remove them when dungeon is actually placed in world
|
||||
world.waitForRegionAndJoin(Vector2i(x, y), reader.size) {
|
||||
val entities = world.parent.entityIndex.query(AABBi(Vector2i(x, y), Vector2i(x, y) + reader.size))
|
||||
val entities = world.parent.entityIndex.query(AABBi(Vector2i(x, y), Vector2i(x, y) + reader.size).toDoubleAABB())
|
||||
|
||||
for (entity in entities) {
|
||||
if (entity !is TileEntity)
|
||||
|
@ -34,7 +34,6 @@ import ru.dbotthepony.kstarbound.defs.tile.TileDefinition
|
||||
import ru.dbotthepony.kstarbound.defs.tile.isEmptyTile
|
||||
import ru.dbotthepony.kstarbound.defs.tile.isNotEmptyTile
|
||||
import ru.dbotthepony.kstarbound.world.Direction
|
||||
import ru.dbotthepony.kstarbound.world.Side
|
||||
import ru.dbotthepony.kstarbound.world.World
|
||||
import kotlin.math.PI
|
||||
|
||||
|
@ -57,12 +57,18 @@ val Registry.Entry<TileDefinition>.isObjectPlatformTile: Boolean
|
||||
val Registry.Entry<TileDefinition>.supportsModifiers: Boolean
|
||||
get() = !value.isMeta && value.supportsMods
|
||||
|
||||
/**
|
||||
* whenever modifier is empty (always supported), or material supports it
|
||||
*/
|
||||
fun Registry.Entry<TileDefinition>.supportsModifier(modifier: Registry.Entry<TileModifierDefinition>): Boolean {
|
||||
return !value.isMeta && value.supportsMods && !modifier.value.isMeta
|
||||
return modifier == BuiltinMetaMaterials.EMPTY_MOD || value.supportsModifier(modifier)
|
||||
}
|
||||
|
||||
/**
|
||||
* whenever modifier is empty (always supported), or material supports it
|
||||
*/
|
||||
fun Registry.Entry<TileDefinition>.supportsModifier(modifier: Registry.Ref<TileModifierDefinition>): Boolean {
|
||||
return !value.isMeta && value.supportsMods && modifier.isPresent && !modifier.value!!.isMeta
|
||||
return !modifier.isPresent || value.supportsModifier(modifier.entry!!)
|
||||
}
|
||||
|
||||
val Registry.Entry<LiquidDefinition>.isEmptyLiquid: Boolean
|
||||
@ -80,6 +86,18 @@ val Registry.Ref<LiquidDefinition>.orEmptyLiquid: Registry.Entry<LiquidDefinitio
|
||||
val Registry.Ref<LiquidDefinition>.isNotEmptyLiquid: Boolean
|
||||
get() = !isEmptyLiquid
|
||||
|
||||
val Registry.Ref<TileModifierDefinition>.isEmptyModifier: Boolean
|
||||
get() = entry == null || entry == BuiltinMetaMaterials.EMPTY_MOD
|
||||
|
||||
val Registry.Ref<TileModifierDefinition>.isNotEmptyModifier: Boolean
|
||||
get() = !isEmptyModifier
|
||||
|
||||
val Registry.Entry<TileModifierDefinition>.isEmptyModifier: Boolean
|
||||
get() = this == BuiltinMetaMaterials.EMPTY_MOD
|
||||
|
||||
val Registry.Entry<TileModifierDefinition>.isNotEmptyModifier: Boolean
|
||||
get() = !isEmptyModifier
|
||||
|
||||
val Registry.Ref<TileModifierDefinition>.orEmptyModifier: Registry.Entry<TileModifierDefinition>
|
||||
get() = entry ?: BuiltinMetaMaterials.EMPTY_MOD
|
||||
|
||||
|
@ -4,6 +4,7 @@ import com.google.common.collect.ImmutableList
|
||||
import ru.dbotthepony.kommons.math.RGBAColor
|
||||
import ru.dbotthepony.kommons.util.Either
|
||||
import ru.dbotthepony.kstarbound.Globals
|
||||
import ru.dbotthepony.kstarbound.Registry
|
||||
import ru.dbotthepony.kstarbound.defs.AssetReference
|
||||
import ru.dbotthepony.kstarbound.world.physics.CollisionType
|
||||
import ru.dbotthepony.kstarbound.defs.IThingWithDescription
|
||||
@ -56,6 +57,10 @@ data class TileDefinition(
|
||||
require(materialId > 0) { "Invalid tile ID $materialId" }
|
||||
}
|
||||
|
||||
fun supportsModifier(modifier: Registry.Entry<TileModifierDefinition>): Boolean {
|
||||
return !isMeta && !modifier.value.isMeta && supportsMods
|
||||
}
|
||||
|
||||
val actualDamageTable: TileDamageConfig by lazy {
|
||||
val dmg = damageTable.value ?: TileDamageConfig.EMPTY
|
||||
|
||||
|
@ -52,6 +52,7 @@ import ru.dbotthepony.kstarbound.network.packets.clientbound.SystemShipDestroyPa
|
||||
import ru.dbotthepony.kstarbound.network.packets.clientbound.SystemWorldStartPacket
|
||||
import ru.dbotthepony.kstarbound.network.packets.clientbound.SystemWorldUpdatePacket
|
||||
import ru.dbotthepony.kstarbound.network.packets.clientbound.TileDamageUpdatePacket
|
||||
import ru.dbotthepony.kstarbound.network.packets.clientbound.TileModificationFailurePacket
|
||||
import ru.dbotthepony.kstarbound.network.packets.clientbound.UniverseTimeUpdatePacket
|
||||
import ru.dbotthepony.kstarbound.network.packets.clientbound.UpdateWorldPropertiesPacket
|
||||
import ru.dbotthepony.kstarbound.network.packets.clientbound.WorldStartPacket
|
||||
@ -63,6 +64,7 @@ import ru.dbotthepony.kstarbound.network.packets.serverbound.DamageTileGroupPack
|
||||
import ru.dbotthepony.kstarbound.network.packets.serverbound.EntityInteractPacket
|
||||
import ru.dbotthepony.kstarbound.network.packets.serverbound.FindUniqueEntityPacket
|
||||
import ru.dbotthepony.kstarbound.network.packets.serverbound.FlyShipPacket
|
||||
import ru.dbotthepony.kstarbound.network.packets.serverbound.ModifyTileListPacket
|
||||
import ru.dbotthepony.kstarbound.network.packets.serverbound.PlayerWarpPacket
|
||||
import ru.dbotthepony.kstarbound.network.packets.serverbound.RequestDropPacket
|
||||
import ru.dbotthepony.kstarbound.network.packets.serverbound.WorldClientStateUpdatePacket
|
||||
@ -443,7 +445,7 @@ class PacketRegistry(val isLegacy: Boolean) {
|
||||
LEGACY.add(LegacyTileUpdatePacket::read)
|
||||
LEGACY.skip("TileLiquidUpdate")
|
||||
LEGACY.add(::TileDamageUpdatePacket)
|
||||
LEGACY.skip("TileModificationFailure")
|
||||
LEGACY.add(::TileModificationFailurePacket)
|
||||
LEGACY.add(::GiveItemPacket)
|
||||
LEGACY.add(::EnvironmentUpdatePacket)
|
||||
LEGACY.skip("UpdateTileProtection")
|
||||
@ -454,7 +456,7 @@ class PacketRegistry(val isLegacy: Boolean) {
|
||||
LEGACY.add(PongPacket::read)
|
||||
|
||||
// Packets sent world client -> world server
|
||||
LEGACY.skip("ModifyTileList")
|
||||
LEGACY.add(::ModifyTileListPacket)
|
||||
LEGACY.add(::DamageTileGroupPacket)
|
||||
LEGACY.skip("CollectLiquid")
|
||||
LEGACY.add(::RequestDropPacket)
|
||||
|
@ -0,0 +1,24 @@
|
||||
package ru.dbotthepony.kstarbound.network.packets.clientbound
|
||||
|
||||
import ru.dbotthepony.kommons.io.readCollection
|
||||
import ru.dbotthepony.kommons.io.readVector2i
|
||||
import ru.dbotthepony.kommons.io.writeCollection
|
||||
import ru.dbotthepony.kommons.io.writeStruct2i
|
||||
import ru.dbotthepony.kommons.vector.Vector2i
|
||||
import ru.dbotthepony.kstarbound.client.ClientConnection
|
||||
import ru.dbotthepony.kstarbound.network.IClientPacket
|
||||
import ru.dbotthepony.kstarbound.world.TileModification
|
||||
import java.io.DataInputStream
|
||||
import java.io.DataOutputStream
|
||||
|
||||
class TileModificationFailurePacket(val modifications: Collection<Pair<Vector2i, TileModification>>) : IClientPacket {
|
||||
constructor(stream: DataInputStream, isLegacy: Boolean) : this(stream.readCollection { stream.readVector2i() to TileModification.read(stream, isLegacy) })
|
||||
|
||||
override fun write(stream: DataOutputStream, isLegacy: Boolean) {
|
||||
stream.writeCollection(modifications) { stream.writeStruct2i(it.first); it.second.write(stream, isLegacy) }
|
||||
}
|
||||
|
||||
override fun play(connection: ClientConnection) {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
}
|
@ -0,0 +1,36 @@
|
||||
package ru.dbotthepony.kstarbound.network.packets.serverbound
|
||||
|
||||
import ru.dbotthepony.kommons.io.readCollection
|
||||
import ru.dbotthepony.kommons.io.readVector2i
|
||||
import ru.dbotthepony.kommons.io.writeCollection
|
||||
import ru.dbotthepony.kommons.io.writeStruct2i
|
||||
import ru.dbotthepony.kommons.vector.Vector2i
|
||||
import ru.dbotthepony.kstarbound.network.IServerPacket
|
||||
import ru.dbotthepony.kstarbound.network.packets.clientbound.TileModificationFailurePacket
|
||||
import ru.dbotthepony.kstarbound.world.TileModification
|
||||
import ru.dbotthepony.kstarbound.server.ServerConnection
|
||||
import java.io.DataInputStream
|
||||
import java.io.DataOutputStream
|
||||
|
||||
class ModifyTileListPacket(val modifications: Collection<Pair<Vector2i, TileModification>>, val allowEntityOverlap: Boolean) : IServerPacket {
|
||||
constructor(stream: DataInputStream, isLegacy: Boolean) : this(stream.readCollection { stream.readVector2i() to TileModification.read(stream, isLegacy) }, stream.readBoolean())
|
||||
|
||||
override fun write(stream: DataOutputStream, isLegacy: Boolean) {
|
||||
stream.writeCollection(modifications) { stream.writeStruct2i(it.first); it.second.write(stream, isLegacy) }
|
||||
stream.writeBoolean(allowEntityOverlap)
|
||||
}
|
||||
|
||||
override fun play(connection: ServerConnection) {
|
||||
val inWorld = connection.enqueue {
|
||||
val unapplied = applyTileModifications(modifications, allowEntityOverlap)
|
||||
|
||||
if (unapplied.isNotEmpty()) {
|
||||
connection.send(TileModificationFailurePacket(unapplied))
|
||||
}
|
||||
}
|
||||
|
||||
if (!inWorld) {
|
||||
connection.send(TileModificationFailurePacket(modifications))
|
||||
}
|
||||
}
|
||||
}
|
@ -53,8 +53,14 @@ class ServerConnection(val server: StarboundServer, type: ConnectionType) : Conn
|
||||
|
||||
// packets which interact with world must be
|
||||
// executed on world's thread
|
||||
fun enqueue(task: ServerWorld.() -> Unit) {
|
||||
return tracker?.enqueue(task) ?: LOGGER.warn("$this tried to interact with world, but they are not in one.")
|
||||
fun enqueue(task: ServerWorld.() -> Unit): Boolean {
|
||||
val isInWorld = tracker?.enqueue(task) != null
|
||||
|
||||
if (!isInWorld) {
|
||||
LOGGER.warn("$this tried to interact with world, but they are not in one.")
|
||||
}
|
||||
|
||||
return isInWorld
|
||||
}
|
||||
|
||||
lateinit var shipWorld: ServerWorld
|
||||
|
@ -45,13 +45,14 @@ class LegacyWorldStorage(val loader: Loader) : WorldStorage() {
|
||||
return loader(key).thenApplyAsync(Function {
|
||||
it.map {
|
||||
val reader = DataInputStream(BufferedInputStream(InflaterInputStream(ByteArrayInputStream(it))))
|
||||
reader.skipBytes(3)
|
||||
val generationLevel = reader.readVarInt()
|
||||
val tileSerializationVersion = reader.readVarInt()
|
||||
|
||||
val result = Object2DArray.nulls<ImmutableCell>(CHUNK_SIZE, CHUNK_SIZE)
|
||||
|
||||
for (y in 0 until CHUNK_SIZE) {
|
||||
for (x in 0 until CHUNK_SIZE) {
|
||||
result[x, y] = MutableCell().read(reader).immutable()
|
||||
result[x, y] = MutableCell().readLegacy(reader, tileSerializationVersion).immutable()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -2,7 +2,6 @@ package ru.dbotthepony.kstarbound.server.world
|
||||
|
||||
import it.unimi.dsi.fastutil.objects.ObjectAVLTreeSet
|
||||
import it.unimi.dsi.fastutil.objects.ObjectArrayList
|
||||
import it.unimi.dsi.fastutil.objects.ObjectArraySet
|
||||
import kotlinx.coroutines.channels.Channel
|
||||
import kotlinx.coroutines.future.await
|
||||
import kotlinx.coroutines.launch
|
||||
@ -38,7 +37,6 @@ import ru.dbotthepony.kstarbound.network.LegacyNetworkCellState
|
||||
import ru.dbotthepony.kstarbound.network.packets.clientbound.TileDamageUpdatePacket
|
||||
import ru.dbotthepony.kstarbound.util.random.random
|
||||
import ru.dbotthepony.kstarbound.util.random.staticRandomDouble
|
||||
import ru.dbotthepony.kstarbound.world.CHUNK_SIZE
|
||||
import ru.dbotthepony.kstarbound.world.CHUNK_SIZE_FF
|
||||
import ru.dbotthepony.kstarbound.world.Chunk
|
||||
import ru.dbotthepony.kstarbound.world.ChunkPos
|
||||
@ -404,7 +402,7 @@ class ServerChunk(world: ServerWorld, pos: ChunkPos) : Chunk<ServerWorld, Server
|
||||
fun damageTile(pos: Vector2i, isBackground: Boolean, sourcePosition: Vector2d, damage: TileDamage, source: AbstractEntity? = null): DamageResult {
|
||||
val cell = cells.value[pos.x, pos.y]
|
||||
|
||||
if (cell.isIndestructible || cell.tile(isBackground).material.value.isMeta) {
|
||||
if (cell.tile(isBackground).material.value.isMeta) {
|
||||
return DamageResult(TileDamageResult.NONE)
|
||||
}
|
||||
|
||||
@ -426,7 +424,6 @@ class ServerChunk(world: ServerWorld, pos: ChunkPos) : Chunk<ServerWorld, Server
|
||||
}
|
||||
|
||||
health.damage(params, sourcePosition, damage)
|
||||
onTileHealthUpdate(pos.x, pos.y, isBackground, health)
|
||||
|
||||
if (health.isDead) {
|
||||
val drops = ArrayList<ItemDescriptor>()
|
||||
@ -466,8 +463,11 @@ class ServerChunk(world: ServerWorld, pos: ChunkPos) : Chunk<ServerWorld, Server
|
||||
}
|
||||
|
||||
setCell(pos.x, pos.y, mCell.immutable())
|
||||
health.reset()
|
||||
onTileHealthUpdate(pos.x, pos.y, isBackground, health)
|
||||
return DamageResult(result, copyHealth, cell)
|
||||
} else {
|
||||
onTileHealthUpdate(pos.x, pos.y, isBackground, health)
|
||||
return DamageResult(result, health, cell)
|
||||
}
|
||||
}
|
||||
@ -549,9 +549,9 @@ class ServerChunk(world: ServerWorld, pos: ChunkPos) : Chunk<ServerWorld, Server
|
||||
if (state == ChunkState.FULL) {
|
||||
val unloadable = world.entityIndex
|
||||
.query(
|
||||
aabb,
|
||||
aabbd,
|
||||
filter = Predicate { it.isApplicableForUnloading && !it.isRemote && aabbd.isInside(it.position) },
|
||||
distinct = true, withEdges = false)
|
||||
withEdges = false)
|
||||
|
||||
world.storage.saveCells(pos, copyCells())
|
||||
world.storage.saveEntities(pos, unloadable)
|
||||
|
@ -12,6 +12,7 @@ import ru.dbotthepony.kommons.util.AABB
|
||||
import ru.dbotthepony.kommons.util.AABBi
|
||||
import ru.dbotthepony.kommons.util.IStruct2i
|
||||
import ru.dbotthepony.kommons.vector.Vector2d
|
||||
import ru.dbotthepony.kommons.vector.Vector2i
|
||||
import ru.dbotthepony.kstarbound.Globals
|
||||
import ru.dbotthepony.kstarbound.Starbound
|
||||
import ru.dbotthepony.kstarbound.defs.WarpAction
|
||||
@ -26,6 +27,7 @@ import ru.dbotthepony.kstarbound.defs.world.WorldTemplate
|
||||
import ru.dbotthepony.kstarbound.json.builder.JsonFactory
|
||||
import ru.dbotthepony.kstarbound.json.jsonArrayOf
|
||||
import ru.dbotthepony.kstarbound.network.IPacket
|
||||
import ru.dbotthepony.kstarbound.world.TileModification
|
||||
import ru.dbotthepony.kstarbound.network.packets.StepUpdatePacket
|
||||
import ru.dbotthepony.kstarbound.network.packets.clientbound.PlayerWarpResultPacket
|
||||
import ru.dbotthepony.kstarbound.network.packets.clientbound.UpdateWorldPropertiesPacket
|
||||
@ -174,7 +176,7 @@ class ServerWorld private constructor(
|
||||
damage = damage.copy(type = TileDamageType.PROTECTED)
|
||||
|
||||
if (!isBackground) {
|
||||
for (entity in entitiesAtTile(pos, distinct = false)) {
|
||||
for (entity in entitiesAtTile(pos)) {
|
||||
if (!damagedEntities.add(entity)) continue
|
||||
|
||||
val occupySpaces = entity.occupySpaces.stream()
|
||||
@ -217,6 +219,30 @@ class ServerWorld private constructor(
|
||||
return topMost
|
||||
}
|
||||
|
||||
fun applyTileModifications(modifications: Collection<Pair<Vector2i, TileModification>>, allowEntityOverlap: Boolean, ignoreTileProtection: Boolean = false): List<Pair<Vector2i, TileModification>> {
|
||||
val unapplied = ArrayList(modifications)
|
||||
var size: Int
|
||||
|
||||
do {
|
||||
size = unapplied.size
|
||||
val itr = unapplied.iterator()
|
||||
|
||||
for ((pos, modification) in itr) {
|
||||
val cell = getCell(pos)
|
||||
|
||||
if (!ignoreTileProtection && cell.dungeonId in protectedDungeonIDs)
|
||||
continue
|
||||
|
||||
if (modification.allowed(this, pos, allowEntityOverlap)) {
|
||||
modification.apply(this, pos, allowEntityOverlap)
|
||||
itr.remove()
|
||||
}
|
||||
}
|
||||
} while(unapplied.isNotEmpty() && size != unapplied.size)
|
||||
|
||||
return unapplied
|
||||
}
|
||||
|
||||
override fun tick() {
|
||||
try {
|
||||
if (clients.isEmpty() && isBusy <= 0) {
|
||||
@ -352,8 +378,8 @@ class ServerWorld private constructor(
|
||||
tickets.addAll(region)
|
||||
region.forEach { it.chunk.await() }
|
||||
|
||||
foundGround = matchCells(spawnRect) {
|
||||
it.foreground.material.value.collisionKind != CollisionType.NONE
|
||||
foundGround = anyCellSatisfies(spawnRect) { tx, ty, tcell ->
|
||||
tcell.foreground.material.value.collisionKind != CollisionType.NONE
|
||||
}
|
||||
|
||||
if (foundGround) {
|
||||
@ -382,7 +408,7 @@ class ServerWorld private constructor(
|
||||
tickets.addAll(region)
|
||||
region.forEach { it.chunk.await() }
|
||||
|
||||
if (!matchCells(spawnRect) { it.foreground.material.value.collisionKind != CollisionType.NONE } && spawnRect.maxs.y < geometry.size.y) {
|
||||
if (!anyCellSatisfies(spawnRect) { tx, ty, tcell -> tcell.foreground.material.value.collisionKind != CollisionType.NONE } && spawnRect.maxs.y < geometry.size.y) {
|
||||
LOGGER.info("Found appropriate spawn position at $pos")
|
||||
return pos
|
||||
}
|
||||
|
@ -205,9 +205,7 @@ class ServerWorldTracker(val world: ServerWorld, val client: ServerConnection, p
|
||||
val trackingEntities = ObjectAVLTreeSet<AbstractEntity>()
|
||||
|
||||
for (region in trackingRegions) {
|
||||
// we don't care about distinct values here, since
|
||||
// we handle this by ourselves
|
||||
trackingEntities.addAll(world.entityIndex.query(region, filter = { it.connectionID != client.connectionID }, distinct = false))
|
||||
trackingEntities.addAll(world.entityIndex.query(region.toDoubleAABB(), filter = { it.connectionID != client.connectionID }))
|
||||
}
|
||||
|
||||
val unseen = IntArrayList(entityVersions.keys)
|
||||
|
@ -47,15 +47,15 @@ abstract class AbstractPerlinNoise(val parameters: PerlinNoiseParameters) {
|
||||
protected val g3_1 by lazy(LazyThreadSafetyMode.NONE) { DoubleArray(parameters.scale * 2 + 2) }
|
||||
protected val g3_2 by lazy(LazyThreadSafetyMode.NONE) { DoubleArray(parameters.scale * 2 + 2) }
|
||||
|
||||
private var init = false
|
||||
private val initLock = Any()
|
||||
|
||||
init {
|
||||
if (parameters.seed != null && parameters.type != PerlinNoiseParameters.Type.UNITIALIZED) {
|
||||
init(parameters.seed)
|
||||
}
|
||||
}
|
||||
|
||||
private var init = false
|
||||
private val initLock = Any()
|
||||
|
||||
protected fun checkInit() {
|
||||
check(hasSeedSpecified) { "No noise seed specified" }
|
||||
|
||||
|
@ -1,6 +0,0 @@
|
||||
package ru.dbotthepony.kstarbound.world
|
||||
|
||||
enum class Side {
|
||||
LEFT,
|
||||
RIGHT;
|
||||
}
|
@ -1,16 +1,15 @@
|
||||
package ru.dbotthepony.kstarbound.world
|
||||
|
||||
import it.unimi.dsi.fastutil.ints.IntAVLTreeSet
|
||||
import it.unimi.dsi.fastutil.longs.Long2ObjectFunction
|
||||
import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap
|
||||
import it.unimi.dsi.fastutil.longs.LongArrayList
|
||||
import it.unimi.dsi.fastutil.objects.Object2IntAVLTreeMap
|
||||
import it.unimi.dsi.fastutil.objects.ObjectAVLTreeSet
|
||||
import ru.dbotthepony.kommons.util.AABB
|
||||
import ru.dbotthepony.kommons.util.AABBi
|
||||
import ru.dbotthepony.kommons.util.KOptional
|
||||
import java.util.concurrent.atomic.AtomicInteger
|
||||
import java.util.concurrent.locks.ReentrantLock
|
||||
import java.util.function.Predicate
|
||||
import kotlin.concurrent.withLock
|
||||
|
||||
// After some thinking, I decided to go with separate spatial index over
|
||||
// using chunk/chunkmap as spatial indexing of entities (just like original engine does).
|
||||
@ -58,7 +57,7 @@ class SpatialIndex<T>(val geometry: WorldGeometry) {
|
||||
|
||||
inner class Entry(val value: T) : Comparable<Entry> {
|
||||
private val sectors = Object2IntAVLTreeMap<Sector>()
|
||||
private val id = counter.getAndIncrement()
|
||||
val id = counter.getAndIncrement()
|
||||
private val fixtures = ArrayList<Fixture>(1)
|
||||
|
||||
// default fixture since in most cases it should be enough
|
||||
@ -247,19 +246,34 @@ class SpatialIndex<T>(val geometry: WorldGeometry) {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* [filter] might be invoked for same entry multiple times, regardless of [distinct]
|
||||
*/
|
||||
fun query(rect: AABBi, filter: Predicate<T> = Predicate { true }, distinct: Boolean = true, withEdges: Boolean = true): List<T> {
|
||||
return query(rect.toDoubleAABB(), filter, distinct, withEdges)
|
||||
fun query(rect: AABB, filter: Predicate<T> = Predicate { true }, withEdges: Boolean = true): List<T> {
|
||||
val entriesDirect = ArrayList<T>()
|
||||
|
||||
iterate(rect, withEdges = withEdges, visitor = {
|
||||
if (filter.test(it)) entriesDirect.add(it)
|
||||
})
|
||||
|
||||
return entriesDirect
|
||||
}
|
||||
|
||||
/**
|
||||
* [filter] might be invoked for same entry multiple times, regardless of [distinct]
|
||||
*/
|
||||
fun query(rect: AABB, filter: Predicate<T> = Predicate { true }, distinct: Boolean = true, withEdges: Boolean = true): List<T> {
|
||||
val entries = ArrayList<Entry>()
|
||||
val entriesDirect = ArrayList<T>()
|
||||
fun any(rect: AABB, filter: Predicate<T> = Predicate { true }, withEdges: Boolean = true): Boolean {
|
||||
return walk(rect, withEdges = withEdges, visitor = {
|
||||
if (filter.test(it)) KOptional(true) else KOptional()
|
||||
}).orElse(false)
|
||||
}
|
||||
|
||||
fun all(rect: AABB, filter: Predicate<T> = Predicate { true }, withEdges: Boolean = true): Boolean {
|
||||
return walk(rect, withEdges = withEdges, visitor = {
|
||||
if (!filter.test(it)) KOptional(false) else KOptional()
|
||||
}).orElse(true)
|
||||
}
|
||||
|
||||
fun iterate(rect: AABB, visitor: (T) -> Unit, withEdges: Boolean = true) {
|
||||
walk<Unit>(rect, { visitor(it); KOptional() }, withEdges)
|
||||
}
|
||||
|
||||
fun <V> walk(rect: AABB, visitor: (T) -> KOptional<V>, withEdges: Boolean = true): KOptional<V> {
|
||||
val seen = IntAVLTreeSet()
|
||||
|
||||
for (actualRegion in geometry.split(rect).first) {
|
||||
val xMin = geometry.x.chunkFromCell(actualRegion.mins.x)
|
||||
@ -272,42 +286,18 @@ class SpatialIndex<T>(val geometry: WorldGeometry) {
|
||||
for (y in yMin .. yMax) {
|
||||
val sector = map[index(x, y)] ?: continue
|
||||
|
||||
if (distinct) {
|
||||
for (entry in sector.entries) {
|
||||
if (filter.test(entry.value) && entry.intersects(actualRegion, withEdges)) {
|
||||
entries.add(entry)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for (entry in sector.entries) {
|
||||
if (filter.test(entry.value) && entry.intersects(actualRegion, withEdges)) {
|
||||
entriesDirect.add(entry.value)
|
||||
}
|
||||
for (entry in sector.entries) {
|
||||
if (entry.intersects(actualRegion, withEdges) && seen.add(entry.id)) {
|
||||
val visit = visitor(entry.value)
|
||||
|
||||
if (visit.isPresent)
|
||||
return visit
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (distinct) {
|
||||
if (entries.isEmpty())
|
||||
return listOf()
|
||||
|
||||
entries.sort()
|
||||
|
||||
val entries0 = ArrayList<T>(entries.size)
|
||||
var previous: Entry? = null
|
||||
|
||||
for (entry in entries) {
|
||||
if (entry != previous) {
|
||||
previous = entry
|
||||
entries0.add(entry.value)
|
||||
}
|
||||
}
|
||||
|
||||
return entries0
|
||||
} else {
|
||||
return entriesDirect
|
||||
}
|
||||
return KOptional()
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,313 @@
|
||||
package ru.dbotthepony.kstarbound.world
|
||||
|
||||
import ru.dbotthepony.kommons.util.AABB
|
||||
import ru.dbotthepony.kommons.vector.Vector2d
|
||||
import ru.dbotthepony.kommons.vector.Vector2i
|
||||
import ru.dbotthepony.kstarbound.Registries
|
||||
import ru.dbotthepony.kstarbound.Registry
|
||||
import ru.dbotthepony.kstarbound.defs.tile.ARTIFICIAL_DUNGEON_ID
|
||||
import ru.dbotthepony.kstarbound.defs.tile.BuiltinMetaMaterials
|
||||
import ru.dbotthepony.kstarbound.defs.tile.LiquidDefinition
|
||||
import ru.dbotthepony.kstarbound.defs.tile.TileDefinition
|
||||
import ru.dbotthepony.kstarbound.defs.tile.TileModifierDefinition
|
||||
import ru.dbotthepony.kstarbound.defs.tile.isEmptyModifier
|
||||
import ru.dbotthepony.kstarbound.defs.tile.isEmptyTile
|
||||
import ru.dbotthepony.kstarbound.defs.tile.isNotEmptyLiquid
|
||||
import ru.dbotthepony.kstarbound.defs.tile.isNotEmptyModifier
|
||||
import ru.dbotthepony.kstarbound.defs.tile.isNotEmptyTile
|
||||
import ru.dbotthepony.kstarbound.defs.tile.orEmptyModifier
|
||||
import ru.dbotthepony.kstarbound.defs.tile.supportsModifier
|
||||
import ru.dbotthepony.kstarbound.io.readNullable
|
||||
import ru.dbotthepony.kstarbound.io.readNullableFloat
|
||||
import ru.dbotthepony.kstarbound.server.world.ServerWorld
|
||||
import ru.dbotthepony.kstarbound.world.api.TileColor
|
||||
import ru.dbotthepony.kstarbound.world.entities.DynamicEntity
|
||||
import ru.dbotthepony.kstarbound.world.entities.tile.TileEntity
|
||||
import java.io.DataInputStream
|
||||
import java.io.DataOutputStream
|
||||
import java.util.function.Predicate
|
||||
|
||||
sealed class TileModification {
|
||||
object Invalid : TileModification() {
|
||||
override fun write(stream: DataOutputStream, isLegacy: Boolean) {
|
||||
stream.writeByte(0)
|
||||
}
|
||||
|
||||
override fun apply(
|
||||
world: World<*, *>,
|
||||
position: Vector2i,
|
||||
allowEntityOverlap: Boolean,
|
||||
) {
|
||||
// do nothing
|
||||
}
|
||||
|
||||
override fun allowed(
|
||||
world: World<*, *>,
|
||||
position: Vector2i,
|
||||
allowEntityOverlap: Boolean,
|
||||
perhaps: Boolean
|
||||
): Boolean {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
abstract fun write(stream: DataOutputStream, isLegacy: Boolean)
|
||||
abstract fun allowed(world: World<*, *>, position: Vector2i, allowEntityOverlap: Boolean, perhaps: Boolean = false): Boolean
|
||||
abstract fun apply(world: World<*, *>, position: Vector2i, allowEntityOverlap: Boolean)
|
||||
|
||||
data class PlaceMaterial(val isBackground: Boolean, val material: Registry.Ref<TileDefinition>, val hueShift: Float?) : TileModification() {
|
||||
constructor(stream: DataInputStream, isLegacy: Boolean) : this(stream.readBoolean(), if (isLegacy) Registries.tiles.ref(stream.readUnsignedShort()) else TODO(), if (isLegacy) stream.readNullable { stream.readUnsignedByte() / 255f } else stream.readNullableFloat())
|
||||
|
||||
override fun write(stream: DataOutputStream, isLegacy: Boolean) {
|
||||
stream.writeByte(1)
|
||||
stream.writeBoolean(isBackground)
|
||||
|
||||
if (isLegacy) {
|
||||
val id = material.key.right.orNull() ?: material.entry?.id ?: throw IllegalStateException("Can't network $material over legacy protocol because it has no pre-defined ID")
|
||||
|
||||
if (id !in 0 .. 65535) {
|
||||
throw IllegalStateException("Can't network $material over legacy protocol because its pre-defined ID is bigger than uint16_t")
|
||||
}
|
||||
|
||||
stream.writeShort(id)
|
||||
|
||||
stream.writeBoolean(hueShift != null)
|
||||
|
||||
if (hueShift != null) {
|
||||
stream.writeByte((hueShift * 360f / 255f).toInt())
|
||||
}
|
||||
} else {
|
||||
// registries name->id mapping should be networked on join
|
||||
TODO()
|
||||
|
||||
// stream.writeNullableFloat(hueShift)
|
||||
}
|
||||
}
|
||||
|
||||
override fun allowed(
|
||||
world: World<*, *>,
|
||||
position: Vector2i,
|
||||
allowEntityOverlap: Boolean,
|
||||
perhaps: Boolean
|
||||
): Boolean {
|
||||
if (material.isEmptyTile || material.value!!.isMeta)
|
||||
return false
|
||||
|
||||
if (isBackground && material.value!!.foregroundOnly)
|
||||
return false
|
||||
|
||||
val (x, y) = position
|
||||
val cell = world.getCell(x, y)
|
||||
val tile = cell.tile(isBackground)
|
||||
|
||||
if (tile.material.isNotEmptyTile)
|
||||
return false
|
||||
|
||||
if (!isBackground) {
|
||||
val rect = AABB(Vector2d(x.toDouble(), y.toDouble()), Vector2d(x + 1.0, y + 1.0))
|
||||
|
||||
if (world.entityIndex.any(rect, Predicate { it is TileEntity && position in it.occupySpaces })) {
|
||||
return false
|
||||
}
|
||||
|
||||
if (!allowEntityOverlap && world.entityIndex.any(rect, Predicate { it is DynamicEntity && it.movement.computeCollisionAABB().intersect(rect) })) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return perhaps ||
|
||||
world.geometry.x.cell(x - 1) == x ||
|
||||
world.geometry.y.cell(x + 1) == x ||
|
||||
world.geometry.y.cell(y - 1) == y ||
|
||||
world.geometry.y.cell(y + 1) == y ||
|
||||
world.anyCellSatisfies(x, y, 1) { tx, ty, tcell ->
|
||||
tx != x && ty != y && (tcell.foreground.material.value.isConnectable || tcell.background.material.value.isConnectable)
|
||||
}
|
||||
}
|
||||
|
||||
override fun apply(
|
||||
world: World<*, *>,
|
||||
position: Vector2i,
|
||||
allowEntityOverlap: Boolean,
|
||||
) {
|
||||
val material = material.entry!!
|
||||
val cell = world.getCell(position).mutable()
|
||||
val tile = cell.tile(isBackground)
|
||||
tile.material = material
|
||||
tile.hueShift = hueShift ?: world.template.cellInfo(position).blockBiome?.hueShift(material) ?: 0f
|
||||
tile.color = TileColor.DEFAULT
|
||||
|
||||
if (material.isEmptyTile) {
|
||||
// remove modifier if removing tile
|
||||
tile.modifier = BuiltinMetaMaterials.EMPTY_MOD
|
||||
tile.modifierHueShift = 0f
|
||||
} else if (isBackground && cell.liquid.isInfinite) {
|
||||
cell.liquid.isInfinite = false
|
||||
cell.liquid.pressure = 1f
|
||||
} else if (!isBackground && material.value.blocksLiquidFlow) {
|
||||
cell.liquid.reset()
|
||||
}
|
||||
|
||||
cell.dungeonId = ARTIFICIAL_DUNGEON_ID
|
||||
world.setCell(position, cell.immutable())
|
||||
}
|
||||
}
|
||||
|
||||
data class PlaceModifier(val isBackground: Boolean, val modifier: Registry.Ref<TileModifierDefinition>, val hueShift: Float?) : TileModification() {
|
||||
constructor(stream: DataInputStream, isLegacy: Boolean) : this(stream.readBoolean(), if (isLegacy) Registries.tileModifiers.ref(stream.readUnsignedShort()) else TODO(), if (isLegacy) stream.readNullable { stream.readUnsignedByte() / 255f } else stream.readNullableFloat())
|
||||
|
||||
override fun write(stream: DataOutputStream, isLegacy: Boolean) {
|
||||
stream.writeByte(2)
|
||||
stream.writeBoolean(isBackground)
|
||||
|
||||
if (isLegacy) {
|
||||
val id = modifier.key.right.orNull() ?: modifier.entry?.id ?: throw IllegalStateException("Can't network $modifier over legacy protocol because it has no pre-defined ID")
|
||||
|
||||
if (id !in 0 .. 65535) {
|
||||
throw IllegalStateException("Can't network $modifier over legacy protocol because its pre-defined ID is bigger than uint16_t")
|
||||
}
|
||||
|
||||
stream.writeShort(id)
|
||||
|
||||
stream.writeBoolean(hueShift != null)
|
||||
|
||||
if (hueShift != null) {
|
||||
stream.writeByte((hueShift * 360f / 255f).toInt())
|
||||
}
|
||||
} else {
|
||||
// registries name->id mapping should be networked on join
|
||||
TODO()
|
||||
|
||||
// stream.writeNullableFloat(hueShift)
|
||||
}
|
||||
}
|
||||
|
||||
override fun allowed(
|
||||
world: World<*, *>,
|
||||
position: Vector2i,
|
||||
allowEntityOverlap: Boolean,
|
||||
perhaps: Boolean
|
||||
): Boolean {
|
||||
val cell = world.getCell(position)
|
||||
val tile = cell.tile(isBackground)
|
||||
return modifier.isNotEmptyModifier &&
|
||||
!modifier.value!!.isMeta &&
|
||||
tile.modifier.isEmptyModifier &&
|
||||
tile.material.supportsModifier(modifier)
|
||||
}
|
||||
|
||||
override fun apply(world: World<*, *>, position: Vector2i, allowEntityOverlap: Boolean) {
|
||||
val cell = world.getCell(position).mutable()
|
||||
val tile = cell.tile(isBackground)
|
||||
tile.modifier = modifier.orEmptyModifier
|
||||
world.setCell(position, cell)
|
||||
}
|
||||
}
|
||||
|
||||
data class Paint(val isBackground: Boolean, val color: TileColor) : TileModification() {
|
||||
constructor(stream: DataInputStream, isLegacy: Boolean) : this(stream.readBoolean(), TileColor.entries[stream.readUnsignedByte()])
|
||||
|
||||
override fun write(stream: DataOutputStream, isLegacy: Boolean) {
|
||||
stream.writeByte(3)
|
||||
stream.writeBoolean(isBackground)
|
||||
stream.writeByte(color.ordinal)
|
||||
}
|
||||
|
||||
override fun allowed(
|
||||
world: World<*, *>,
|
||||
position: Vector2i,
|
||||
allowEntityOverlap: Boolean,
|
||||
perhaps: Boolean
|
||||
): Boolean {
|
||||
val tile = world.getCell(position).tile(isBackground)
|
||||
val material = tile.material.value
|
||||
return tile.hueShift != 0f || tile.color != this.color && material.renderParameters.multiColored
|
||||
}
|
||||
|
||||
override fun apply(world: World<*, *>, position: Vector2i, allowEntityOverlap: Boolean) {
|
||||
val cell = world.getCell(position)
|
||||
val tile = cell.tile(isBackground).mutable()
|
||||
tile.hueShift = 0f
|
||||
tile.color = this.color
|
||||
world.setCell(position, cell)
|
||||
}
|
||||
}
|
||||
|
||||
data class Pour(val state: Registry.Ref<LiquidDefinition>, val level: Float) : TileModification() {
|
||||
constructor(stream: DataInputStream, isLegacy: Boolean) : this(if (isLegacy) Registries.liquid.ref(stream.readUnsignedByte()) else TODO(), stream.readFloat())
|
||||
|
||||
override fun write(stream: DataOutputStream, isLegacy: Boolean) {
|
||||
stream.writeByte(4)
|
||||
|
||||
if (isLegacy) {
|
||||
val id = state.key.right.orNull() ?: state.entry?.id ?: throw IllegalStateException("Can't network $state over legacy protocol because it has no pre-defined ID")
|
||||
|
||||
if (id !in 0..255) {
|
||||
throw IllegalStateException("Can't network $state over legacy protocol because its pre-defined ID is bigger than uint8_t")
|
||||
}
|
||||
|
||||
stream.writeByte(id)
|
||||
} else {
|
||||
// registries name->id mapping should be networked on join
|
||||
TODO()
|
||||
}
|
||||
|
||||
stream.writeFloat(level)
|
||||
}
|
||||
|
||||
override fun allowed(
|
||||
world: World<*, *>,
|
||||
position: Vector2i,
|
||||
allowEntityOverlap: Boolean,
|
||||
perhaps: Boolean
|
||||
): Boolean {
|
||||
if (state.isEmpty)
|
||||
return false
|
||||
|
||||
val cell = world.getCell(position)
|
||||
|
||||
if (cell.liquid.state.isNotEmptyLiquid && cell.liquid.isInfinite)
|
||||
return false // it makes no sense to try to pour liquid into infinite source
|
||||
|
||||
if (cell.liquid.state.isNotEmptyLiquid && cell.liquid.state != state.entry)
|
||||
return false // it makes also makes no sense to magically replace liquid what is already there
|
||||
|
||||
// while checks above makes vanilla client look stupid when it tries to pour liquids into other
|
||||
// liquids, we must think better than vanilla client.
|
||||
|
||||
return !cell.foreground.material.value.collisionKind.isSolidCollision
|
||||
}
|
||||
|
||||
override fun apply(world: World<*, *>, position: Vector2i, allowEntityOverlap: Boolean) {
|
||||
if (state.isEmpty)
|
||||
return
|
||||
|
||||
val state = state.entry!!
|
||||
val cell = world.getCell(position).mutable()
|
||||
|
||||
if (cell.liquid.state == state) {
|
||||
cell.liquid.level += level
|
||||
} else {
|
||||
cell.liquid.reset()
|
||||
cell.liquid.state = state
|
||||
cell.liquid.level = level
|
||||
cell.liquid.pressure = 1f
|
||||
}
|
||||
|
||||
world.setCell(position, cell)
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
fun read(stream: DataInputStream, isLegacy: Boolean): TileModification {
|
||||
return when (val type = stream.readUnsignedByte()) {
|
||||
0 -> Invalid
|
||||
1 -> PlaceMaterial(stream, isLegacy)
|
||||
2 -> PlaceModifier(stream, isLegacy)
|
||||
3 -> Paint(stream, isLegacy)
|
||||
4 -> Pour(stream, isLegacy)
|
||||
else -> throw IllegalArgumentException("Unknown tile modification type $type!")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -17,7 +17,10 @@ import ru.dbotthepony.kommons.util.AABB
|
||||
import ru.dbotthepony.kommons.util.AABBi
|
||||
import ru.dbotthepony.kommons.vector.Vector2d
|
||||
import ru.dbotthepony.kommons.vector.Vector2i
|
||||
import ru.dbotthepony.kstarbound.Registry
|
||||
import ru.dbotthepony.kstarbound.Starbound
|
||||
import ru.dbotthepony.kstarbound.defs.tile.TileDefinition
|
||||
import ru.dbotthepony.kstarbound.defs.tile.isNotEmptyTile
|
||||
import ru.dbotthepony.kstarbound.defs.world.WorldStructure
|
||||
import ru.dbotthepony.kstarbound.defs.world.WorldTemplate
|
||||
import ru.dbotthepony.kstarbound.json.mergeJson
|
||||
@ -36,8 +39,6 @@ import ru.dbotthepony.kstarbound.world.entities.tile.TileEntity
|
||||
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.getBlockPlatforms
|
||||
import ru.dbotthepony.kstarbound.world.physics.getBlocksMarchingSquares
|
||||
import java.util.concurrent.CopyOnWriteArrayList
|
||||
import java.util.concurrent.atomic.AtomicInteger
|
||||
import java.util.concurrent.locks.ReentrantLock
|
||||
@ -87,6 +88,11 @@ abstract class World<This : World<This, ChunkType>, ChunkType : Chunk<This, Chun
|
||||
return chunk.getCell(ix and CHUNK_SIZE_MASK, iy and CHUNK_SIZE_MASK)
|
||||
}
|
||||
|
||||
fun getCellDirect(ix: Int, iy: Int): AbstractCell {
|
||||
val chunk = get(geometry.x.chunkFromCell(ix), geometry.y.chunkFromCell(iy)) ?: return AbstractCell.NULL
|
||||
return chunk.getCell(ix and CHUNK_SIZE_MASK, iy and CHUNK_SIZE_MASK)
|
||||
}
|
||||
|
||||
fun setCell(x: Int, y: Int, cell: AbstractCell, state: ChunkState): Boolean {
|
||||
val ix = geometry.x.cell(x)
|
||||
val iy = geometry.y.cell(y)
|
||||
@ -302,21 +308,26 @@ abstract class World<This : World<This, ChunkType>, ChunkType : Chunk<This, Chun
|
||||
|
||||
abstract val eventLoop: BlockableEventLoop
|
||||
|
||||
fun entitiesAtTile(pos: Vector2i, filter: Predicate<TileEntity> = Predicate { true }, distinct: Boolean = true): List<TileEntity> {
|
||||
fun entitiesAtTile(pos: Vector2i, filter: Predicate<TileEntity> = Predicate { true }): List<TileEntity> {
|
||||
return entityIndex.query(
|
||||
AABBi(pos, pos + Vector2i.POSITIVE_XY),
|
||||
distinct = distinct,
|
||||
AABBi(pos, pos + Vector2i.POSITIVE_XY).toDoubleAABB(),
|
||||
filter = { it is TileEntity && pos in it.occupySpaces && filter.test(it) }
|
||||
) as List<TileEntity>
|
||||
}
|
||||
|
||||
fun matchCells(aabb: AABBi, predicate: Predicate<AbstractCell>): Boolean {
|
||||
for (split in geometry.split(aabb).first) {
|
||||
for (x in split.mins.x .. split.maxs.x) {
|
||||
for (y in split.mins.x .. split.maxs.x) {
|
||||
if (predicate.test(chunkMap.getCell(x, y))) {
|
||||
return true
|
||||
}
|
||||
fun interface CellPredicate {
|
||||
fun test(x: Int, y: Int, cell: AbstractCell): Boolean
|
||||
}
|
||||
|
||||
fun anyCellSatisfies(aabb: AABBi, predicate: CellPredicate): Boolean {
|
||||
for (x in aabb.mins.x .. aabb.maxs.x) {
|
||||
for (y in aabb.mins.x .. aabb.maxs.x) {
|
||||
val ix = geometry.x.cell(x)
|
||||
val iy = geometry.x.cell(y)
|
||||
|
||||
|
||||
if (predicate.test(ix, iy, chunkMap.getCellDirect(ix, iy))) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -324,13 +335,29 @@ abstract class World<This : World<This, ChunkType>, ChunkType : Chunk<This, Chun
|
||||
return false
|
||||
}
|
||||
|
||||
fun matchCells(aabb: AABB, predicate: Predicate<AbstractCell>): Boolean {
|
||||
for (split in geometry.split(aabb).first) {
|
||||
for (x in split.mins.x.toInt() .. split.maxs.x.roundToInt()) {
|
||||
for (y in split.mins.y.toInt() .. split.maxs.y.roundToInt()) {
|
||||
if (predicate.test(chunkMap.getCell(x, y))) {
|
||||
return true
|
||||
}
|
||||
fun anyCellSatisfies(aabb: AABB, predicate: CellPredicate): Boolean {
|
||||
for (x in aabb.mins.x.toInt() .. aabb.maxs.x.roundToInt()) {
|
||||
for (y in aabb.mins.y.toInt() .. aabb.maxs.y.roundToInt()) {
|
||||
val ix = geometry.x.cell(x)
|
||||
val iy = geometry.x.cell(y)
|
||||
|
||||
if (predicate.test(ix, iy, chunkMap.getCellDirect(ix, iy))) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
fun anyCellSatisfies(x: Int, y: Int, distance: Int, predicate: CellPredicate): Boolean {
|
||||
for (tx in x - distance .. x + distance) {
|
||||
for (ty in y - distance .. y + distance) {
|
||||
val ix = geometry.x.cell(tx)
|
||||
val iy = geometry.x.cell(ty)
|
||||
|
||||
if (predicate.test(ix, iy, chunkMap.getCellDirect(ix, iy))) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -394,8 +421,8 @@ abstract class World<This : World<This, ChunkType>, ChunkType : Chunk<This, Chun
|
||||
* of world generation, everything else is kinda okay to be performed
|
||||
* on main world thread, so concurrent access is not needed for now.
|
||||
*
|
||||
* ArrayChunkMap does not need synchronization since concurrent write
|
||||
* won't cause any observable side effects by concurrent readers.
|
||||
* ArrayChunkMap does ~not need~ needs synchronization too, unless we use CopyOnWriteArrayList
|
||||
* for "existing" chunks list.
|
||||
*/
|
||||
private const val CONCURRENT_SPARSE_CHUNK_MAP = false
|
||||
}
|
||||
|
@ -27,8 +27,7 @@ sealed class AbstractCell {
|
||||
// value of 0 points to null, 1 points to 0, and so on
|
||||
abstract val envBiome: Int
|
||||
|
||||
// whenever if cell ignores any attempts to damage it
|
||||
abstract val isIndestructible: Boolean
|
||||
abstract val biomeTransition: Boolean
|
||||
|
||||
abstract fun immutable(): ImmutableCell
|
||||
abstract fun mutable(): MutableCell
|
||||
@ -59,7 +58,7 @@ sealed class AbstractCell {
|
||||
stream.writeShort(dungeonId)
|
||||
stream.writeByte(blockBiome)
|
||||
stream.writeByte(envBiome)
|
||||
stream.writeBoolean(isIndestructible)
|
||||
stream.writeBoolean(biomeTransition)
|
||||
|
||||
stream.write(0) // unknown
|
||||
}
|
||||
|
@ -11,7 +11,7 @@ data class ImmutableCell(
|
||||
override val dungeonId: Int = NO_DUNGEON_ID,
|
||||
override val blockBiome: Int = 0,
|
||||
override val envBiome: Int = 0,
|
||||
override val isIndestructible: Boolean = false,
|
||||
override val biomeTransition: Boolean = false,
|
||||
) : AbstractCell() {
|
||||
override fun immutable(): ImmutableCell {
|
||||
return this
|
||||
@ -31,6 +31,6 @@ data class ImmutableCell(
|
||||
}
|
||||
|
||||
override fun mutable(): MutableCell {
|
||||
return MutableCell(foreground.mutable(), background.mutable(), liquid.mutable(), dungeonId, blockBiome, envBiome, isIndestructible)
|
||||
return MutableCell(foreground.mutable(), background.mutable(), liquid.mutable(), dungeonId, blockBiome, envBiome, biomeTransition)
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,6 @@
|
||||
package ru.dbotthepony.kstarbound.world.api
|
||||
|
||||
import ru.dbotthepony.kommons.io.readVector2i
|
||||
import ru.dbotthepony.kstarbound.defs.tile.NO_DUNGEON_ID
|
||||
import java.io.DataInputStream
|
||||
|
||||
@ -11,21 +12,34 @@ data class MutableCell(
|
||||
override var dungeonId: Int = NO_DUNGEON_ID,
|
||||
override var blockBiome: Int = 0,
|
||||
override var envBiome: Int = 0,
|
||||
override var isIndestructible: Boolean = false,
|
||||
override var biomeTransition: Boolean = false,
|
||||
) : AbstractCell() {
|
||||
fun read(stream: DataInputStream): MutableCell {
|
||||
fun readLegacy(stream: DataInputStream, version: Int = 419): MutableCell {
|
||||
foreground.read(stream)
|
||||
background.read(stream)
|
||||
liquid.read(stream)
|
||||
|
||||
stream.skipNBytes(1) // collisionMap
|
||||
stream.skipNBytes(1) // foreground.material.value.collisionKind
|
||||
|
||||
dungeonId = stream.readUnsignedShort()
|
||||
blockBiome = stream.readUnsignedByte()
|
||||
envBiome = stream.readUnsignedByte()
|
||||
isIndestructible = stream.readBoolean()
|
||||
|
||||
stream.skipNBytes(1) // unknown
|
||||
if (version < 417) {
|
||||
biomeTransition = false
|
||||
} else {
|
||||
biomeTransition = stream.readBoolean()
|
||||
}
|
||||
|
||||
if (version < 418) {
|
||||
stream.skipNBytes(1) // leftover
|
||||
} else {
|
||||
// TODO: root source
|
||||
if (stream.readBoolean()) {
|
||||
stream.readVector2i()
|
||||
}
|
||||
}
|
||||
|
||||
return this
|
||||
}
|
||||
|
||||
@ -37,7 +51,7 @@ data class MutableCell(
|
||||
}
|
||||
|
||||
override fun immutable(): ImmutableCell {
|
||||
return POOL.intern(ImmutableCell(foreground.immutable(), background.immutable(), liquid.immutable(), dungeonId, blockBiome, envBiome, isIndestructible))
|
||||
return POOL.intern(ImmutableCell(foreground.immutable(), background.immutable(), liquid.immutable(), dungeonId, blockBiome, envBiome, biomeTransition))
|
||||
}
|
||||
|
||||
override fun mutable(): MutableCell {
|
||||
|
@ -2,6 +2,7 @@ package ru.dbotthepony.kstarbound.world.api
|
||||
|
||||
import com.google.common.collect.ImmutableMap
|
||||
|
||||
// uint8_t
|
||||
enum class TileColor {
|
||||
DEFAULT,
|
||||
RED,
|
||||
|
@ -4,6 +4,7 @@ import com.google.gson.JsonArray
|
||||
import com.google.gson.JsonElement
|
||||
import org.apache.logging.log4j.LogManager
|
||||
import ru.dbotthepony.kommons.io.koptional
|
||||
import ru.dbotthepony.kommons.util.AABB
|
||||
import ru.dbotthepony.kommons.util.KOptional
|
||||
import ru.dbotthepony.kommons.vector.Vector2d
|
||||
import ru.dbotthepony.kstarbound.Starbound
|
||||
@ -106,6 +107,11 @@ abstract class AbstractEntity(path: String) : JsonDriven(path), Comparable<Abstr
|
||||
protected var spatialEntry: SpatialIndex<AbstractEntity>.Entry? = null
|
||||
private set
|
||||
|
||||
/**
|
||||
* Used for spatial index
|
||||
*/
|
||||
abstract val metaBoundingBox: AABB
|
||||
|
||||
open fun onNetworkUpdate() {
|
||||
|
||||
}
|
||||
|
@ -2,6 +2,7 @@ package ru.dbotthepony.kstarbound.world.entities
|
||||
|
||||
import ru.dbotthepony.kommons.math.RGBAColor
|
||||
import ru.dbotthepony.kommons.util.AABB
|
||||
import ru.dbotthepony.kstarbound.Globals
|
||||
import ru.dbotthepony.kstarbound.client.StarboundClient
|
||||
import ru.dbotthepony.kstarbound.client.render.LayeredRenderer
|
||||
import ru.dbotthepony.kstarbound.client.render.RenderLayer
|
||||
@ -27,25 +28,38 @@ abstract class DynamicEntity(path: String) : AbstractEntity(path) {
|
||||
movement.updateFixtures()
|
||||
}
|
||||
|
||||
private var fixturesChangeset = -1
|
||||
|
||||
override fun tick() {
|
||||
super.tick()
|
||||
|
||||
if (isRemote && networkGroup.upstream.isInterpolating) {
|
||||
movement.updateFixtures()
|
||||
}
|
||||
|
||||
if (fixturesChangeset != movement.fixturesChangeset) {
|
||||
fixturesChangeset = movement.fixturesChangeset
|
||||
metaFixture!!.move(metaBoundingBox)
|
||||
}
|
||||
}
|
||||
|
||||
protected var metaFixture: SpatialIndex<AbstractEntity>.Entry.Fixture? = null
|
||||
private set
|
||||
|
||||
override fun onJoinWorld(world: World<*, *>) {
|
||||
super.onJoinWorld(world)
|
||||
world.dynamicEntities.add(this)
|
||||
movement.initialize(world, spatialEntry)
|
||||
forceChunkRepos = true
|
||||
metaFixture = spatialEntry!!.Fixture()
|
||||
}
|
||||
|
||||
override fun onRemove(world: World<*, *>, reason: RemovalReason) {
|
||||
super.onRemove(world, reason)
|
||||
world.dynamicEntities.remove(this)
|
||||
movement.remove()
|
||||
metaFixture?.remove()
|
||||
metaFixture = null
|
||||
}
|
||||
|
||||
override fun render(client: StarboundClient, layers: LayeredRenderer) {
|
||||
|
@ -121,6 +121,9 @@ class ItemDropEntity() : DynamicEntity("/") {
|
||||
|
||||
private var stayAliveFor = -1.0
|
||||
|
||||
override val metaBoundingBox: AABB
|
||||
get() = AABB(position - Vector2d(0.5, 0.5), position + Vector2d(0.5, 0.5))
|
||||
|
||||
override fun tick() {
|
||||
super.tick()
|
||||
|
||||
|
@ -71,6 +71,33 @@ open class MovementController() {
|
||||
}
|
||||
}
|
||||
|
||||
fun computeCollisionAABB(): AABB {
|
||||
val poly = movementParameters.collisionPoly ?: return AABB.ZERO + position
|
||||
|
||||
if (poly.isLeft) {
|
||||
if (poly.left().isEmpty)
|
||||
return AABB.ZERO + position
|
||||
|
||||
return (poly.left().rotate(rotation) + position).aabb
|
||||
} else {
|
||||
if (poly.right().isEmpty())
|
||||
return AABB.ZERO + position
|
||||
else if (poly.right().first().isEmpty)
|
||||
return AABB.ZERO + position
|
||||
|
||||
var build = (poly.right().first().rotate(rotation) + position).aabb
|
||||
|
||||
for (i in 1 until poly.right().size) {
|
||||
val gPoly = poly.right()[i]
|
||||
|
||||
if (gPoly.isNotEmpty)
|
||||
build = build.combine((gPoly.rotate(rotation) + position).aabb)
|
||||
}
|
||||
|
||||
return build
|
||||
}
|
||||
}
|
||||
|
||||
open fun shouldCollideWithType(type: CollisionType): Boolean {
|
||||
return type !== CollisionType.NONE
|
||||
}
|
||||
@ -117,6 +144,10 @@ open class MovementController() {
|
||||
fun updateFixtures() {
|
||||
val spatialEntry = spatialEntry ?: return
|
||||
fixturesChangeset++
|
||||
val poly = movementParameters.collisionPoly ?: return
|
||||
if (poly.isLeft && poly.left().isEmpty) return
|
||||
if (poly.isRight && poly.right().none { it.isNotEmpty }) return
|
||||
|
||||
val localHitboxes = computeLocalHitboxes()
|
||||
|
||||
while (fixtures.size > localHitboxes.size) {
|
||||
|
@ -2,6 +2,7 @@ package ru.dbotthepony.kstarbound.world.entities.player
|
||||
|
||||
import com.google.gson.JsonElement
|
||||
import ru.dbotthepony.kommons.io.writeBinaryString
|
||||
import ru.dbotthepony.kommons.util.AABB
|
||||
import ru.dbotthepony.kommons.util.KOptional
|
||||
import ru.dbotthepony.kommons.util.getValue
|
||||
import ru.dbotthepony.kommons.util.setValue
|
||||
@ -102,28 +103,8 @@ class PlayerEntity() : HumanoidActorEntity("/") {
|
||||
networkGroup.upstream.add(techController.networkGroup)
|
||||
}
|
||||
|
||||
private var fixturesChangeset = -1
|
||||
private var metaFixture: SpatialIndex<AbstractEntity>.Entry.Fixture? = null
|
||||
|
||||
override fun onJoinWorld(world: World<*, *>) {
|
||||
super.onJoinWorld(world)
|
||||
metaFixture = spatialEntry!!.Fixture()
|
||||
}
|
||||
|
||||
override fun onRemove(world: World<*, *>, reason: RemovalReason) {
|
||||
super.onRemove(world, reason)
|
||||
metaFixture?.remove()
|
||||
metaFixture = null
|
||||
}
|
||||
|
||||
override fun tick() {
|
||||
super.tick()
|
||||
|
||||
if (fixturesChangeset != movement.fixturesChangeset) {
|
||||
fixturesChangeset = movement.fixturesChangeset
|
||||
metaFixture!!.move(Globals.player.metaBoundBox + position)
|
||||
}
|
||||
}
|
||||
override val metaBoundingBox: AABB
|
||||
get() = Globals.player.metaBoundBox + position
|
||||
|
||||
override val aimPosition: Vector2d
|
||||
get() = Vector2d(xAimPosition, yAimPosition)
|
||||
|
@ -31,8 +31,6 @@ abstract class TileEntity(path: String) : AbstractEntity(path) {
|
||||
yTilePositionNet.addListener(::onPositionUpdated)
|
||||
}
|
||||
|
||||
abstract val metaBoundingBox: AABB
|
||||
|
||||
var xTilePosition: Int
|
||||
get() = xTilePositionNet.get()
|
||||
set(value) {
|
||||
|
@ -94,6 +94,8 @@ class Poly private constructor(val edges: ImmutableList<Line2d>, val vertices: I
|
||||
val aabb: AABB
|
||||
val isEmpty: Boolean
|
||||
get() = vertices.isEmpty()
|
||||
val isNotEmpty: Boolean
|
||||
get() = vertices.isNotEmpty()
|
||||
|
||||
init {
|
||||
if (vertices.isEmpty()) {
|
||||
|
@ -58,11 +58,11 @@ class KarstCaveTerrainSelector(data: Data, parameters: TerrainSelectorParameters
|
||||
}
|
||||
|
||||
private val layers = Caffeine.newBuilder()
|
||||
.maximumSize(256L)
|
||||
.maximumSize(512L)
|
||||
.softValues()
|
||||
.expireAfterAccess(Duration.ofMinutes(2))
|
||||
//.scheduler(Starbound)
|
||||
//.executor(Starbound.EXECUTOR)
|
||||
.expireAfterAccess(Duration.ofSeconds(10))
|
||||
.scheduler(Starbound)
|
||||
.executor(Starbound.EXECUTOR)
|
||||
.build<Int, Layer>(::Layer)
|
||||
|
||||
private inner class Sector(val sector: Vector2i) {
|
||||
@ -127,11 +127,11 @@ class KarstCaveTerrainSelector(data: Data, parameters: TerrainSelectorParameters
|
||||
}
|
||||
|
||||
private val sectors = Caffeine.newBuilder()
|
||||
.maximumSize(64L)
|
||||
.maximumSize(256L)
|
||||
.softValues()
|
||||
.expireAfterAccess(Duration.ofMinutes(2))
|
||||
//.scheduler(Starbound)
|
||||
//.executor(Starbound.EXECUTOR)
|
||||
.expireAfterAccess(Duration.ofSeconds(10))
|
||||
.scheduler(Starbound)
|
||||
.executor(Starbound.EXECUTOR)
|
||||
.build<Vector2i, Sector>(::Sector)
|
||||
|
||||
override fun get(x: Int, y: Int): Double {
|
||||
|
@ -178,11 +178,11 @@ class WormCaveTerrainSelector(data: Data, parameters: TerrainSelectorParameters)
|
||||
}
|
||||
|
||||
private val sectors = Caffeine.newBuilder()
|
||||
.maximumSize(64L)
|
||||
.maximumSize(256L)
|
||||
.softValues()
|
||||
.expireAfterAccess(Duration.ofMinutes(2))
|
||||
//.scheduler(Starbound)
|
||||
//.executor(Starbound.EXECUTOR)
|
||||
.expireAfterAccess(Duration.ofSeconds(10))
|
||||
.scheduler(Starbound)
|
||||
.executor(Starbound.EXECUTOR)
|
||||
.build<Vector2i, Sector>(::Sector)
|
||||
|
||||
override fun get(x: Int, y: Int): Double {
|
||||
|
Loading…
Reference in New Issue
Block a user