From 7cd0f5e173e8c891cc444ed0534d50b91819da0c Mon Sep 17 00:00:00 2001 From: DBotThePony Date: Wed, 10 Apr 2024 22:41:41 +0700 Subject: [PATCH] TileModification packets --- ADDITIONS.md | 1 + .../network/packets/ChunkCellsPacket.kt | 2 +- .../kstarbound/defs/dungeon/DungeonPart.kt | 2 +- .../defs/object/ObjectOrientation.kt | 1 - .../defs/tile/BuiltinMetaMaterials.kt | 22 +- .../kstarbound/defs/tile/TileDefinition.kt | 5 + .../kstarbound/network/PacketRegistry.kt | 6 +- .../TileModificationFailurePacket.kt | 24 ++ .../serverbound/ModifyTileListPacket.kt | 36 ++ .../kstarbound/server/ServerConnection.kt | 10 +- .../server/world/LegacyWorldStorage.kt | 5 +- .../kstarbound/server/world/ServerChunk.kt | 12 +- .../kstarbound/server/world/ServerWorld.kt | 34 +- .../server/world/ServerWorldTracker.kt | 4 +- .../util/random/AbstractPerlinNoise.kt | 6 +- .../ru/dbotthepony/kstarbound/world/Side.kt | 6 - .../kstarbound/world/SpatialIndex.kt | 82 ++--- .../kstarbound/world/TileModification.kt | 313 ++++++++++++++++++ .../ru/dbotthepony/kstarbound/world/World.kt | 69 ++-- .../kstarbound/world/api/AbstractCell.kt | 5 +- .../kstarbound/world/api/ImmutableCell.kt | 4 +- .../kstarbound/world/api/MutableCell.kt | 26 +- .../kstarbound/world/api/TileColor.kt | 1 + .../world/entities/AbstractEntity.kt | 6 + .../world/entities/DynamicEntity.kt | 14 + .../world/entities/ItemDropEntity.kt | 3 + .../world/entities/MovementController.kt | 31 ++ .../world/entities/player/PlayerEntity.kt | 25 +- .../world/entities/tile/TileEntity.kt | 2 - .../kstarbound/world/physics/Poly.kt | 2 + .../world/terrain/KarstCaveTerrainSelector.kt | 16 +- .../world/terrain/WormCaveTerrainSelector.kt | 8 +- 32 files changed, 636 insertions(+), 147 deletions(-) create mode 100644 src/main/kotlin/ru/dbotthepony/kstarbound/network/packets/clientbound/TileModificationFailurePacket.kt create mode 100644 src/main/kotlin/ru/dbotthepony/kstarbound/network/packets/serverbound/ModifyTileListPacket.kt delete mode 100644 src/main/kotlin/ru/dbotthepony/kstarbound/world/Side.kt create mode 100644 src/main/kotlin/ru/dbotthepony/kstarbound/world/TileModification.kt diff --git a/ADDITIONS.md b/ADDITIONS.md index 369a72b6..6990d228 100644 --- a/ADDITIONS.md +++ b/ADDITIONS.md @@ -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) diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/client/network/packets/ChunkCellsPacket.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/client/network/packets/ChunkCellsPacket.kt index 43f5c84b..e4cc4c05 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/client/network/packets/ChunkCellsPacket.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/client/network/packets/ChunkCellsPacket.kt @@ -15,7 +15,7 @@ import java.io.DataInputStream import java.io.DataOutputStream class ChunkCellsPacket(val pos: ChunkPos, val data: List) : 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(CHUNK_SIZE * CHUNK_SIZE).also { for (x in 0 until CHUNK_SIZE) { for (y in 0 until CHUNK_SIZE) { diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/defs/dungeon/DungeonPart.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/defs/dungeon/DungeonPart.kt index 2282042c..b0f3fe68 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/defs/dungeon/DungeonPart.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/defs/dungeon/DungeonPart.kt @@ -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) diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/defs/object/ObjectOrientation.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/defs/object/ObjectOrientation.kt index 0a343318..442980f7 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/defs/object/ObjectOrientation.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/defs/object/ObjectOrientation.kt @@ -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 diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/defs/tile/BuiltinMetaMaterials.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/defs/tile/BuiltinMetaMaterials.kt index 378d3721..e999cc1c 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/defs/tile/BuiltinMetaMaterials.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/defs/tile/BuiltinMetaMaterials.kt @@ -57,12 +57,18 @@ val Registry.Entry.isObjectPlatformTile: Boolean val Registry.Entry.supportsModifiers: Boolean get() = !value.isMeta && value.supportsMods +/** + * whenever modifier is empty (always supported), or material supports it + */ fun Registry.Entry.supportsModifier(modifier: Registry.Entry): 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.supportsModifier(modifier: Registry.Ref): Boolean { - return !value.isMeta && value.supportsMods && modifier.isPresent && !modifier.value!!.isMeta + return !modifier.isPresent || value.supportsModifier(modifier.entry!!) } val Registry.Entry.isEmptyLiquid: Boolean @@ -80,6 +86,18 @@ val Registry.Ref.orEmptyLiquid: Registry.Entry.isNotEmptyLiquid: Boolean get() = !isEmptyLiquid +val Registry.Ref.isEmptyModifier: Boolean + get() = entry == null || entry == BuiltinMetaMaterials.EMPTY_MOD + +val Registry.Ref.isNotEmptyModifier: Boolean + get() = !isEmptyModifier + +val Registry.Entry.isEmptyModifier: Boolean + get() = this == BuiltinMetaMaterials.EMPTY_MOD + +val Registry.Entry.isNotEmptyModifier: Boolean + get() = !isEmptyModifier + val Registry.Ref.orEmptyModifier: Registry.Entry get() = entry ?: BuiltinMetaMaterials.EMPTY_MOD diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/defs/tile/TileDefinition.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/defs/tile/TileDefinition.kt index 681e7827..76e764be 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/defs/tile/TileDefinition.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/defs/tile/TileDefinition.kt @@ -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): Boolean { + return !isMeta && !modifier.value.isMeta && supportsMods + } + val actualDamageTable: TileDamageConfig by lazy { val dmg = damageTable.value ?: TileDamageConfig.EMPTY diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/network/PacketRegistry.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/network/PacketRegistry.kt index 2fe88d93..73b8f82c 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/network/PacketRegistry.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/network/PacketRegistry.kt @@ -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) diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/network/packets/clientbound/TileModificationFailurePacket.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/network/packets/clientbound/TileModificationFailurePacket.kt new file mode 100644 index 00000000..6e8c6dc5 --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/network/packets/clientbound/TileModificationFailurePacket.kt @@ -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>) : 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") + } +} diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/network/packets/serverbound/ModifyTileListPacket.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/network/packets/serverbound/ModifyTileListPacket.kt new file mode 100644 index 00000000..b695b34d --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/network/packets/serverbound/ModifyTileListPacket.kt @@ -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>, 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)) + } + } +} diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/server/ServerConnection.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/server/ServerConnection.kt index 76ad74ab..1f3af7f3 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/server/ServerConnection.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/server/ServerConnection.kt @@ -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 diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/server/world/LegacyWorldStorage.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/server/world/LegacyWorldStorage.kt index 5c676b56..ebdc3976 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/server/world/LegacyWorldStorage.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/server/world/LegacyWorldStorage.kt @@ -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(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() } } diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/server/world/ServerChunk.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/server/world/ServerChunk.kt index 6fde06ee..9498e83b 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/server/world/ServerChunk.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/server/world/ServerChunk.kt @@ -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() @@ -466,8 +463,11 @@ class ServerChunk(world: ServerWorld, pos: ChunkPos) : Chunk>, allowEntityOverlap: Boolean, ignoreTileProtection: Boolean = false): List> { + 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 } diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/server/world/ServerWorldTracker.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/server/world/ServerWorldTracker.kt index 5d44b253..53fc2395 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/server/world/ServerWorldTracker.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/server/world/ServerWorldTracker.kt @@ -205,9 +205,7 @@ class ServerWorldTracker(val world: ServerWorld, val client: ServerConnection, p val trackingEntities = ObjectAVLTreeSet() 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) diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/util/random/AbstractPerlinNoise.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/util/random/AbstractPerlinNoise.kt index 9921e43f..df4c3296 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/util/random/AbstractPerlinNoise.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/util/random/AbstractPerlinNoise.kt @@ -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" } diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/world/Side.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/world/Side.kt deleted file mode 100644 index a4827384..00000000 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/world/Side.kt +++ /dev/null @@ -1,6 +0,0 @@ -package ru.dbotthepony.kstarbound.world - -enum class Side { - LEFT, - RIGHT; -} diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/world/SpatialIndex.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/world/SpatialIndex.kt index f5f09267..9cd3d041 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/world/SpatialIndex.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/world/SpatialIndex.kt @@ -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(val geometry: WorldGeometry) { inner class Entry(val value: T) : Comparable { private val sectors = Object2IntAVLTreeMap() - private val id = counter.getAndIncrement() + val id = counter.getAndIncrement() private val fixtures = ArrayList(1) // default fixture since in most cases it should be enough @@ -247,19 +246,34 @@ class SpatialIndex(val geometry: WorldGeometry) { } } - /** - * [filter] might be invoked for same entry multiple times, regardless of [distinct] - */ - fun query(rect: AABBi, filter: Predicate = Predicate { true }, distinct: Boolean = true, withEdges: Boolean = true): List { - return query(rect.toDoubleAABB(), filter, distinct, withEdges) + fun query(rect: AABB, filter: Predicate = Predicate { true }, withEdges: Boolean = true): List { + val entriesDirect = ArrayList() + + 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 = Predicate { true }, distinct: Boolean = true, withEdges: Boolean = true): List { - val entries = ArrayList() - val entriesDirect = ArrayList() + fun any(rect: AABB, filter: Predicate = 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 = 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(rect, { visitor(it); KOptional() }, withEdges) + } + + fun walk(rect: AABB, visitor: (T) -> KOptional, withEdges: Boolean = true): KOptional { + val seen = IntAVLTreeSet() for (actualRegion in geometry.split(rect).first) { val xMin = geometry.x.chunkFromCell(actualRegion.mins.x) @@ -272,42 +286,18 @@ class SpatialIndex(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(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() } } diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/world/TileModification.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/world/TileModification.kt new file mode 100644 index 00000000..3793cd3a --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/world/TileModification.kt @@ -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, 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, 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, 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!") + } + } + } +} diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/world/World.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/world/World.kt index c2cb92dc..069bb8c6 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/world/World.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/world/World.kt @@ -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, ChunkType : Chunk, ChunkType : Chunk = Predicate { true }, distinct: Boolean = true): List { + fun entitiesAtTile(pos: Vector2i, filter: Predicate = Predicate { true }): List { 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 } - fun matchCells(aabb: AABBi, predicate: Predicate): 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, ChunkType : Chunk): 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, ChunkType : Chunk.Entry? = null private set + /** + * Used for spatial index + */ + abstract val metaBoundingBox: AABB + open fun onNetworkUpdate() { } diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/world/entities/DynamicEntity.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/world/entities/DynamicEntity.kt index a4ccdf5c..53f4a0f0 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/world/entities/DynamicEntity.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/world/entities/DynamicEntity.kt @@ -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.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) { diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/world/entities/ItemDropEntity.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/world/entities/ItemDropEntity.kt index 131b9ed7..6888071c 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/world/entities/ItemDropEntity.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/world/entities/ItemDropEntity.kt @@ -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() diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/world/entities/MovementController.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/world/entities/MovementController.kt index ec0adfd7..bb1766e0 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/world/entities/MovementController.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/world/entities/MovementController.kt @@ -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) { diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/world/entities/player/PlayerEntity.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/world/entities/player/PlayerEntity.kt index 724b70fd..0140f65b 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/world/entities/player/PlayerEntity.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/world/entities/player/PlayerEntity.kt @@ -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.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) diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/world/entities/tile/TileEntity.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/world/entities/tile/TileEntity.kt index b9b0e3cc..cfadbe8d 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/world/entities/tile/TileEntity.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/world/entities/tile/TileEntity.kt @@ -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) { diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/world/physics/Poly.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/world/physics/Poly.kt index 6db490d9..a85041d3 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/world/physics/Poly.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/world/physics/Poly.kt @@ -94,6 +94,8 @@ class Poly private constructor(val edges: ImmutableList, val vertices: I val aabb: AABB val isEmpty: Boolean get() = vertices.isEmpty() + val isNotEmpty: Boolean + get() = vertices.isNotEmpty() init { if (vertices.isEmpty()) { diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/world/terrain/KarstCaveTerrainSelector.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/world/terrain/KarstCaveTerrainSelector.kt index 41fd35c6..e8446dbc 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/world/terrain/KarstCaveTerrainSelector.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/world/terrain/KarstCaveTerrainSelector.kt @@ -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(::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(::Sector) override fun get(x: Int, y: Int): Double { diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/world/terrain/WormCaveTerrainSelector.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/world/terrain/WormCaveTerrainSelector.kt index 2ea49ba2..bfba2f79 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/world/terrain/WormCaveTerrainSelector.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/world/terrain/WormCaveTerrainSelector.kt @@ -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(::Sector) override fun get(x: Int, y: Int): Double {