diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/client/StarboundClient.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/client/StarboundClient.kt index 867efcda..be76c2f6 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/client/StarboundClient.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/client/StarboundClient.kt @@ -54,7 +54,6 @@ import ru.dbotthepony.kstarbound.client.gl.shader.UberShader import ru.dbotthepony.kstarbound.client.gl.vertex.GeometryType import ru.dbotthepony.kstarbound.client.gl.vertex.VertexBuilder import ru.dbotthepony.kstarbound.client.input.UserInput -import ru.dbotthepony.kstarbound.server.network.packets.TrackedPositionPacket import ru.dbotthepony.kstarbound.client.render.Camera import ru.dbotthepony.kstarbound.client.render.Font import ru.dbotthepony.kstarbound.client.render.LayeredRenderer @@ -947,9 +946,6 @@ class StarboundClient private constructor(val clientID: Int) : Closeable { val activeConnection = activeConnection - if (activeConnection != null && !activeConnection.isLegacy && activeConnection.channel.isOpen) - activeConnection.send(TrackedPositionPacket(camera.pos)) - uberShaderPrograms.forValidRefs { it.viewMatrix = viewportMatrixScreen } fontShaderPrograms.forValidRefs { it.viewMatrix = viewportMatrixScreen } diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/lua/RootBindings.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/lua/RootBindings.kt index 7eb4a1fb..42468772 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/lua/RootBindings.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/lua/RootBindings.kt @@ -243,7 +243,7 @@ private fun materialFootstepSound(context: ExecutionContext, arguments: Argument return } - context.returnBuffer.setTo(GlobalDefaults.client.defaultFootstepSound.random()) + context.returnBuffer.setTo(GlobalDefaults.client.defaultFootstepSound.map({ it }, { it.random() })) } private fun materialHealth(context: ExecutionContext, arguments: ArgumentIterator) { diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/network/Connection.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/network/Connection.kt index 0dd3ccb8..0440845b 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/network/Connection.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/network/Connection.kt @@ -11,6 +11,7 @@ import org.apache.logging.log4j.LogManager import ru.dbotthepony.kommons.io.StreamCodec import ru.dbotthepony.kommons.io.VarIntValueCodec import ru.dbotthepony.kommons.util.AABBi +import ru.dbotthepony.kommons.vector.Vector2d import ru.dbotthepony.kommons.vector.Vector2i import ru.dbotthepony.kstarbound.GlobalDefaults import ru.dbotthepony.kstarbound.network.syncher.BasicNetworkedElement @@ -162,7 +163,7 @@ abstract class Connection(val side: ConnectionSide, val type: ConnectionType) : val clientStateGroup = MasterElement(NetworkedGroup(windowXMin, windowYMin, windowWidth, windowHeight, playerID, clientSpectatingEntities)) // in tiles - fun trackingRegions(): List { + fun trackingTileRegions(): List { val result = ArrayList() val mins = Vector2i(windowXMin.get() - GlobalDefaults.client.windowMonitoringBorder, windowYMin.get() - GlobalDefaults.client.windowMonitoringBorder) @@ -178,7 +179,15 @@ abstract class Connection(val side: ConnectionSide, val type: ConnectionType) : if (playerEntity != null) { // add an extra region the size of the window centered on the player's position to prevent nearby sectors // being unloaded due to camera panning or centering on other entities - result.add(window + Vector2i(playerEntity.position.x.roundToInt(), playerEntity.position.y.roundToInt())) + val diff = Vector2d(window.width / 2.0, window.height / 2.0) + + val pmins = playerEntity.position - diff + val pmaxs = playerEntity.position + diff + + result.add(AABBi( + Vector2i(pmins.x.roundToInt(), pmins.y.roundToInt()), + Vector2i(pmaxs.x.roundToInt(), pmaxs.y.roundToInt()), + )) } for (entity in clientSpectatingEntities.get()) { diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/network/PacketRegistry.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/network/PacketRegistry.kt index 5e148250..ed436944 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/network/PacketRegistry.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/network/PacketRegistry.kt @@ -1,7 +1,6 @@ package ru.dbotthepony.kstarbound.network import io.netty.buffer.ByteBuf -import io.netty.buffer.ByteBufInputStream import io.netty.buffer.ByteBufOutputStream import io.netty.channel.ChannelDuplexHandler import io.netty.channel.ChannelHandlerContext @@ -48,8 +47,6 @@ import ru.dbotthepony.kstarbound.network.packets.serverbound.ClientDisconnectReq import ru.dbotthepony.kstarbound.network.packets.serverbound.FindUniqueEntityPacket import ru.dbotthepony.kstarbound.network.packets.serverbound.WorldClientStateUpdatePacket import ru.dbotthepony.kstarbound.network.packets.serverbound.WorldStartAcknowledgePacket -import ru.dbotthepony.kstarbound.server.network.packets.TrackedPositionPacket -import ru.dbotthepony.kstarbound.server.network.packets.TrackedSizePacket import java.io.BufferedInputStream import java.io.DataInputStream import java.io.DataOutputStream @@ -358,8 +355,6 @@ class PacketRegistry(val isLegacy: Boolean) { NATIVE.add(::JoinWorldPacket) NATIVE.add(::ChunkCellsPacket) NATIVE.add(::ForgetChunkPacket) - NATIVE.add(::TrackedPositionPacket) - NATIVE.add(::TrackedSizePacket) NATIVE.add(::SpawnWorldObjectPacket) NATIVE.add(::ForgetEntityPacket) NATIVE.add(::UniverseTimeUpdatePacket) diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/server/ServerConnection.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/server/ServerConnection.kt index a0fe3639..f7ef2830 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/server/ServerConnection.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/server/ServerConnection.kt @@ -4,21 +4,16 @@ import com.google.gson.JsonObject import io.netty.channel.ChannelHandlerContext import it.unimi.dsi.fastutil.bytes.ByteArrayList import it.unimi.dsi.fastutil.ints.Int2LongOpenHashMap -import it.unimi.dsi.fastutil.ints.Int2ObjectMap import it.unimi.dsi.fastutil.ints.Int2ObjectMaps import it.unimi.dsi.fastutil.io.FastByteArrayOutputStream -import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap +import it.unimi.dsi.fastutil.objects.ObjectArraySet import it.unimi.dsi.fastutil.objects.ObjectLinkedOpenHashSet import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet import org.apache.logging.log4j.LogManager import ru.dbotthepony.kommons.io.ByteKey import ru.dbotthepony.kommons.util.KOptional -import ru.dbotthepony.kommons.util.MailboxExecutorService -import ru.dbotthepony.kommons.vector.Vector2d -import ru.dbotthepony.kstarbound.client.network.packets.ForgetChunkPacket +import ru.dbotthepony.kommons.vector.Vector2i import ru.dbotthepony.kstarbound.client.network.packets.ChunkCellsPacket -import ru.dbotthepony.kstarbound.client.network.packets.ForgetEntityPacket -import ru.dbotthepony.kstarbound.client.network.packets.SpawnWorldObjectPacket import ru.dbotthepony.kstarbound.defs.WarpAlias import ru.dbotthepony.kstarbound.network.Connection import ru.dbotthepony.kstarbound.network.ConnectionSide @@ -28,6 +23,7 @@ import ru.dbotthepony.kstarbound.network.packets.ClientContextUpdatePacket import ru.dbotthepony.kstarbound.network.packets.EntityCreatePacket import ru.dbotthepony.kstarbound.network.packets.EntityUpdateSetPacket import ru.dbotthepony.kstarbound.network.packets.clientbound.LegacyTileArrayUpdatePacket +import ru.dbotthepony.kstarbound.network.packets.clientbound.LegacyTileUpdatePacket import ru.dbotthepony.kstarbound.network.packets.clientbound.PlayerWarpResultPacket import ru.dbotthepony.kstarbound.network.packets.clientbound.ServerDisconnectPacket import ru.dbotthepony.kstarbound.server.world.WorldStorage @@ -37,7 +33,6 @@ import ru.dbotthepony.kstarbound.world.ChunkPos import ru.dbotthepony.kstarbound.world.IChunkListener import ru.dbotthepony.kstarbound.world.api.ImmutableCell import ru.dbotthepony.kstarbound.world.entities.AbstractEntity -import ru.dbotthepony.kstarbound.world.entities.WorldObject import ru.dbotthepony.kstarbound.world.entities.player.PlayerEntity import java.io.DataOutputStream import java.util.HashMap @@ -76,33 +71,6 @@ class ServerConnection(val server: StarboundServer, type: ConnectionType) : Conn return "ServerConnection[ID=$connectionID channel=$channel / $ship]" } - var trackedPosition: Vector2d = Vector2d.ZERO - set(value) { - if (field != value) { - field = value - needsToRecomputeTrackedChunks = true - } - } - - var trackedPositionChunk: ChunkPos = ChunkPos.ZERO - private set - - var trackedChunksWidth = 4 - set(value) { - if (field != value) { - field = value - needsToRecomputeTrackedChunks = true - } - } - - var trackedChunksHeight = 4 - set(value) { - if (field != value) { - field = value - needsToRecomputeTrackedChunks = true - } - } - private val shipChunks = HashMap>() private val modifiedShipChunks = ObjectOpenHashSet() var shipChunkSource by Delegates.notNull() @@ -123,23 +91,17 @@ class ServerConnection(val server: StarboundServer, type: ConnectionType) : Conn shipChunks.putAll(chunks) } - private val tickets = HashMap() + private val tickets = HashMap() private val pendingSend = ObjectLinkedOpenHashSet() - private var needsToRecomputeTrackedChunks = true + private inner class Ticket(val ticket: ServerWorld.ITicket, val pos: ChunkPos) : IChunkListener { + override fun onEntityAdded(entity: AbstractEntity) {} + override fun onEntityRemoved(entity: AbstractEntity) {} - private inner class ChunkListener(val pos: ChunkPos) : IChunkListener { - override fun onEntityAdded(entity: AbstractEntity) { - //if (entity is WorldObject && !isLegacy) - // send(SpawnWorldObjectPacket(entity.uuid, entity.serialize())) - } - - override fun onEntityRemoved(entity: AbstractEntity) { - //send(ForgetEntityPacket(entity.uuid)) - } + val dirtyCells = ObjectArraySet() override fun onCellChanges(x: Int, y: Int, cell: ImmutableCell) { - pendingSend.add(pos) + dirtyCells.add(Vector2i(x, y)) } } @@ -162,7 +124,7 @@ class ServerConnection(val server: StarboundServer, type: ConnectionType) : Conn fun onLeaveWorld() { tasks.clear() - tickets.values.forEach { it.cancel() } + tickets.values.forEach { it.ticket.cancel() } tickets.clear() pendingSend.clear() playerEntity = null @@ -210,7 +172,7 @@ class ServerConnection(val server: StarboundServer, type: ConnectionType) : Conn flush() } - tickets.values.forEach { it.cancel() } + tickets.values.forEach { it.ticket.cancel() } tickets.clear() pendingSend.clear() @@ -226,41 +188,6 @@ class ServerConnection(val server: StarboundServer, type: ConnectionType) : Conn } } - private fun recomputeTrackedChunks() { - val world = world ?: return - val trackedPositionChunk = world.geometry.chunkFromCell(trackedPosition) - needsToRecomputeTrackedChunks = false - // if (trackedPositionChunk == this.trackedPositionChunk) return - - val tracked = ObjectOpenHashSet() - - for (x in -trackedChunksWidth .. trackedChunksWidth) { - for (y in -trackedChunksHeight .. trackedChunksHeight) { - tracked.add(world.geometry.wrap(trackedPositionChunk + ChunkPos(x, y))) - } - } - - val itr = tickets.entries.iterator() - - for ((pos, ticket) in itr) { - if (pos !in tracked) { - send(ForgetChunkPacket(pos)) - pendingSend.remove(pos) - ticket.cancel() - itr.remove() - } - } - - for (pos in tracked) { - if (pos !in tickets) { - val ticket = world.permanentChunkTicket(pos) - tickets[pos] = ticket - ticket.listener = ChunkListener(pos) - pendingSend.add(pos) - } - } - } - private val entityVersions = Int2LongOpenHashMap() init { @@ -281,22 +208,60 @@ class ServerConnection(val server: StarboundServer, type: ConnectionType) : Conn playerEntity = world.entities[playerID.get()] as? PlayerEntity - if (needsToRecomputeTrackedChunks) { - recomputeTrackedChunks() - } + run { + val newTrackedChunks = ObjectArraySet() - val itr = pendingSend.iterator() - - for (pos in itr) { - val chunk = world.chunkMap[pos] ?: continue - - if (isLegacy) { - send(LegacyTileArrayUpdatePacket(chunk)) - } else { - send(ChunkCellsPacket(chunk)) + for (region in trackingTileRegions()) { + newTrackedChunks.addAll(world.geometry.tileRegion2Chunks(region)) } - itr.remove() + val itr = tickets.entries.iterator() + + for ((pos, ticket) in itr) { + if (pos !in newTrackedChunks) { + pendingSend.remove(pos) + ticket.ticket.cancel() + itr.remove() + } else { + if (ticket.dirtyCells.isNotEmpty()) { + val chunk = world.chunkMap[ticket.pos] ?: continue + + for (tilePos in ticket.dirtyCells) { + if (isLegacy) { + send(LegacyTileUpdatePacket(pos.tile + tilePos, chunk.getCell(tilePos).toLegacyNet())) + } + } + + ticket.dirtyCells.clear() + } + } + } + + for (pos in newTrackedChunks) { + if (pos !in tickets) { + val ticket = world.permanentChunkTicket(pos) + val thisTicket = Ticket(ticket, pos) + tickets[pos] = thisTicket + ticket.listener = thisTicket + pendingSend.add(pos) + } + } + } + + run { + val itr = pendingSend.iterator() + + for (pos in itr) { + val chunk = world.chunkMap[pos] ?: continue + + if (isLegacy) { + send(LegacyTileArrayUpdatePacket(chunk)) + } else { + send(ChunkCellsPacket(chunk)) + } + + itr.remove() + } } for ((id, entity) in world.entities) { diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/server/network/packets/TrackedPositionPacket.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/server/network/packets/TrackedPositionPacket.kt deleted file mode 100644 index 5efad3cd..00000000 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/server/network/packets/TrackedPositionPacket.kt +++ /dev/null @@ -1,21 +0,0 @@ -package ru.dbotthepony.kstarbound.server.network.packets - -import ru.dbotthepony.kommons.io.readVector2d -import ru.dbotthepony.kommons.io.writeStruct2d -import ru.dbotthepony.kommons.vector.Vector2d -import ru.dbotthepony.kstarbound.network.IServerPacket -import ru.dbotthepony.kstarbound.server.ServerConnection -import java.io.DataInputStream -import java.io.DataOutputStream - -data class TrackedPositionPacket(val pos: Vector2d) : IServerPacket { - constructor(stream: DataInputStream, isLegacy: Boolean) : this(stream.readVector2d()) - - override fun write(stream: DataOutputStream, isLegacy: Boolean) { - stream.writeStruct2d(pos) - } - - override fun play(connection: ServerConnection) { - connection.trackedPosition = pos - } -} \ No newline at end of file diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/server/network/packets/TrackedSizePacket.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/server/network/packets/TrackedSizePacket.kt deleted file mode 100644 index 47d086ba..00000000 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/server/network/packets/TrackedSizePacket.kt +++ /dev/null @@ -1,25 +0,0 @@ -package ru.dbotthepony.kstarbound.server.network.packets - -import ru.dbotthepony.kstarbound.network.IServerPacket -import ru.dbotthepony.kstarbound.server.ServerConnection -import java.io.DataInputStream -import java.io.DataOutputStream - -data class TrackedSizePacket(val width: Int, val height: Int) : IServerPacket { - constructor(stream: DataInputStream, isLegacy: Boolean) : this(stream.readUnsignedByte(), stream.readUnsignedByte()) - - init { - require(width in 1 .. 12) { "Bad chunk width to track: $width" } - require(height in 1 .. 12) { "Bad chunk height to track: $height" } - } - - override fun write(stream: DataOutputStream, isLegacy: Boolean) { - stream.writeByte(width) - stream.writeByte(height) - } - - override fun play(connection: ServerConnection) { - connection.trackedChunksWidth = width - connection.trackedChunksHeight = height - } -} diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/server/world/ServerWorld.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/server/world/ServerWorld.kt index a8c5bd9a..d37a14f0 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/server/world/ServerWorld.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/server/world/ServerWorld.kt @@ -61,7 +61,6 @@ class ServerWorld private constructor( player.world?.removePlayer(player) player.world = this player.worldStartAcknowledged = false - player.trackedPosition = playerSpawnPosition if (player.isLegacy) { val (skyData, skyVersion) = sky.networkedGroup.write(isLegacy = true) diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/world/CoordinateMapper.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/world/CoordinateMapper.kt index 6e9c2b07..5007f3fd 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/world/CoordinateMapper.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/world/CoordinateMapper.kt @@ -1,5 +1,6 @@ package ru.dbotthepony.kstarbound.world +import ru.dbotthepony.kommons.vector.Vector2i import ru.dbotthepony.kstarbound.math.divideUp fun positiveModulo(a: Int, b: Int): Int { @@ -29,6 +30,20 @@ abstract class CoordinateMapper { fun chunkFromCell(value: Float): Int = chunkFromCell(value.toInt()) fun chunkFromCell(value: Double): Int = chunkFromCell(value.toInt()) + data class SplitResult(val rangeA: Vector2i, val rangeB: Vector2i?, val offset: Int) + + /** + * One of: + * * returns provided range as-is if it is contained in this range + * * splits in two if it wraps around one of edges + * * clamps if _this_ range is contained in provided range + */ + fun split(range: IntRange): SplitResult { + return split(range.first, range.last) + } + + abstract fun split(first: Int, last: Int): SplitResult + // inside world bounds abstract fun inBoundsCell(value: Int): Boolean abstract fun inBoundsChunk(value: Int): Boolean @@ -54,6 +69,41 @@ abstract class CoordinateMapper { override fun cell(value: Double): Double = positiveModulo(value, cells) override fun cell(value: Float): Float = positiveModulo(value, cells) override fun chunk(value: Int): Int = positiveModulo(value, chunks) + + override fun split(first: Int, last: Int): SplitResult { + if (first >= last) { + // point or empty range + val wrap = cell(first) + return SplitResult(Vector2i(wrap, wrap), null, 0) + } else if (first <= 0 && last >= cells) { + // covers entire world along this axis + return SplitResult(Vector2i(0, cells - 1), null, 0) + } else if (first >= 0 && last < cells) { + // within range along this axis + return SplitResult(Vector2i(first, last), null, 0) + } else { + val newFirst = cell(first) + val newLast = cell(last) + + if (first < 0) { + // wrapped around left edge + + return SplitResult( + Vector2i(0, newLast), + Vector2i(newFirst, cells - 1), + newFirst - cells + ) + } else { + // wrapped around right edge + + return SplitResult( + Vector2i(newFirst, cells - 1), + Vector2i(0, newLast), + -newLast - 1 + ) + } + } + } } class Clamper(private val cells: Int) : CoordinateMapper() { @@ -71,6 +121,11 @@ abstract class CoordinateMapper { return value in 0 until chunks } + override fun split(first: Int, last: Int): SplitResult { + // just clamp + return SplitResult(Vector2i(cell(first), cell(last)), null, 0) + } + override fun cell(value: Int): Int { return value.coerceIn(0, cells - 1) } diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/world/WorldGeometry.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/world/WorldGeometry.kt index a65f8e95..904a78e3 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/world/WorldGeometry.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/world/WorldGeometry.kt @@ -1,7 +1,10 @@ package ru.dbotthepony.kstarbound.world +import it.unimi.dsi.fastutil.objects.ObjectArrayList +import it.unimi.dsi.fastutil.objects.ObjectArraySet import ru.dbotthepony.kommons.io.readVector2i import ru.dbotthepony.kommons.io.writeStruct2i +import ru.dbotthepony.kommons.util.AABBi import ru.dbotthepony.kommons.util.IStruct2d import ru.dbotthepony.kommons.util.IStruct2f import ru.dbotthepony.kommons.util.IStruct2i @@ -68,4 +71,60 @@ data class WorldGeometry(val size: Vector2i, val loopX: Boolean, val loopY: Bool if (x == pos.x && y == pos.y) return pos.toLong() return ChunkPos.toLong(x, y) } + + // AABB + offset; offset allows to reconstruct original coordinate + // as if it weren't wrapped around world edge + fun split(region: AABBi): Pair, Vector2i> { + val splitX = x.split(region.mins.x, region.maxs.x) + val splitY = y.split(region.mins.y, region.maxs.y) + val offset = Vector2i(splitX.offset, splitY.offset) + + if (splitX.rangeB == null && splitY.rangeB == null) { + // confined + return listOf(AABBi(Vector2i(splitX.rangeA.x, splitY.rangeA.x), Vector2i(splitX.rangeA.y, splitY.rangeA.y))) to offset + } else if (splitX.rangeB != null && splitY.rangeB == null) { + // wrapped around X axis + val a = AABBi(Vector2i(splitX.rangeA.x, splitY.rangeA.x), Vector2i(splitX.rangeA.y, splitY.rangeA.y)) + val b = AABBi(Vector2i(splitX.rangeB.x, splitY.rangeA.x), Vector2i(splitX.rangeB.y, splitY.rangeA.y)) + return listOf(a, b) to offset + } else if (splitX.rangeB == null && splitY.rangeB != null) { + // wrapped around Y axis + val a = AABBi(Vector2i(splitX.rangeA.x, splitY.rangeA.x), Vector2i(splitX.rangeA.y, splitY.rangeA.y)) + val b = AABBi(Vector2i(splitX.rangeA.x, splitY.rangeB.x), Vector2i(splitX.rangeA.y, splitY.rangeB.y)) + return listOf(a, b) to offset + } else { + // wrapped around X and Y axis + splitX.rangeB!! + splitY.rangeB!! + + val a = AABBi(Vector2i(splitX.rangeA.x, splitY.rangeA.x), Vector2i(splitX.rangeA.y, splitY.rangeA.y)) + val b = AABBi(Vector2i(splitX.rangeB.x, splitY.rangeA.x), Vector2i(splitX.rangeB.y, splitY.rangeA.y)) + val c = AABBi(Vector2i(splitX.rangeA.x, splitY.rangeB.x), Vector2i(splitX.rangeA.y, splitY.rangeB.y)) + val d = AABBi(Vector2i(splitX.rangeB.x, splitY.rangeB.x), Vector2i(splitX.rangeB.y, splitY.rangeB.y)) + return listOf(a, b, c, d) to offset + } + } + + fun tileRegion2Chunks(region: AABBi): Set { + if (region.mins == region.maxs) + return emptySet() + + val result = ObjectArraySet() + + for (actualRegion in split(region).first) { + val xMin = this.x.chunkFromCell(actualRegion.mins.x) + val xMax = this.x.chunkFromCell(actualRegion.maxs.x) + + val yMin = this.y.chunkFromCell(actualRegion.mins.y) + val yMax = this.y.chunkFromCell(actualRegion.maxs.y) + + for (x in xMin .. xMax) { + for (y in yMin .. yMax) { + result.add(ChunkPos(x, y)) + } + } + } + + return result + } } diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/world/api/AbstractCell.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/world/api/AbstractCell.kt index 617b8599..5f524f5a 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/world/api/AbstractCell.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/world/api/AbstractCell.kt @@ -21,7 +21,7 @@ sealed class AbstractCell { abstract fun immutable(): ImmutableCell abstract fun mutable(): MutableCell - fun toLegacyNet(): LegacyNetworkCellState { + open fun toLegacyNet(): LegacyNetworkCellState { return LegacyNetworkCellState(background.toLegacyNet(), foreground.toLegacyNet(), foreground.material.value.collisionKind, biome, envBiome, liquid.toLegacyNet(), dungeonId) } diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/world/api/AbstractTileState.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/world/api/AbstractTileState.kt index ffc3bffb..aa975d31 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/world/api/AbstractTileState.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/world/api/AbstractTileState.kt @@ -29,7 +29,7 @@ sealed class AbstractTileState { return (modifierHueShift / 360f * 255).toInt() } - fun toLegacyNet(): LegacyNetworkTileState { + open fun toLegacyNet(): LegacyNetworkTileState { if (material.id != null && material.id in 0 .. 65535) { val validMod = modifier?.id != null && modifier!!.id!! in 0 .. 65535 return LegacyNetworkTileState(material.id!!, byteHueShift(), color.ordinal, if (validMod) modifier!!.id!! else 0, if (validMod) byteModifierHueShift() else 0) diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/world/api/ImmutableCell.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/world/api/ImmutableCell.kt index 3560398a..681826dd 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/world/api/ImmutableCell.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/world/api/ImmutableCell.kt @@ -1,5 +1,7 @@ package ru.dbotthepony.kstarbound.world.api +import ru.dbotthepony.kstarbound.network.LegacyNetworkCellState + data class ImmutableCell( override val foreground: ImmutableTileState = AbstractTileState.EMPTY, override val background: ImmutableTileState = AbstractTileState.EMPTY, @@ -14,6 +16,12 @@ data class ImmutableCell( return this } + private val legacyNet by lazy { super.toLegacyNet() } + + override fun toLegacyNet(): LegacyNetworkCellState { + return legacyNet + } + override fun mutable(): MutableCell { return MutableCell(foreground.mutable(), background.mutable(), liquid.mutable(), dungeonId, biome, envBiome, isIndestructible) } diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/world/api/ImmutableTileState.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/world/api/ImmutableTileState.kt index c7b2d302..99ede4f0 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/world/api/ImmutableTileState.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/world/api/ImmutableTileState.kt @@ -4,6 +4,7 @@ import ru.dbotthepony.kstarbound.Registry import ru.dbotthepony.kstarbound.defs.tile.BuiltinMetaMaterials import ru.dbotthepony.kstarbound.defs.tile.MaterialModifier import ru.dbotthepony.kstarbound.defs.tile.TileDefinition +import ru.dbotthepony.kstarbound.network.LegacyNetworkTileState data class ImmutableTileState( override var material: Registry.Entry = BuiltinMetaMaterials.NULL, @@ -16,6 +17,12 @@ data class ImmutableTileState( return this } + private val legacyNet by lazy { super.toLegacyNet() } + + override fun toLegacyNet(): LegacyNetworkTileState { + return legacyNet + } + override fun mutable(): MutableTileState { return MutableTileState(material, modifier, color, hueShift, modifierHueShift) } diff --git a/src/test/kotlin/ru/dbotthepony/kstarbound/test/WorldTests.kt b/src/test/kotlin/ru/dbotthepony/kstarbound/test/WorldTests.kt new file mode 100644 index 00000000..2e5e9921 --- /dev/null +++ b/src/test/kotlin/ru/dbotthepony/kstarbound/test/WorldTests.kt @@ -0,0 +1,35 @@ +package ru.dbotthepony.kstarbound.test + +import org.junit.jupiter.api.DisplayName +import org.junit.jupiter.api.Test +import ru.dbotthepony.kommons.util.AABBi +import ru.dbotthepony.kommons.vector.Vector2i +import ru.dbotthepony.kstarbound.world.WorldGeometry + +object WorldTests { + @Test + @DisplayName("World geometry splitting test") + fun worldGeometrySplit() { + val geometry = WorldGeometry(Vector2i(3000, 2000), true, false) + + val testCases = listOf( + AABBi(Vector2i(4, 4), Vector2i(100, 100)) to listOf(AABBi(Vector2i(4, 4), Vector2i(100, 100))), + AABBi(Vector2i(0, 0), Vector2i(100, 100)) to listOf(AABBi(Vector2i(0, 0), Vector2i(100, 100))), + AABBi(Vector2i(-1, 0), Vector2i(100, 100)) to listOf(AABBi(Vector2i(0, 0), Vector2i(100, 100)), AABBi(Vector2i(2999, 0), Vector2i(2999, 100))), + AABBi(Vector2i(-3, 0), Vector2i(100, 100)) to listOf(AABBi(Vector2i(0, 0), Vector2i(100, 100)), AABBi(Vector2i(2997, 0), Vector2i(2999, 100))), + AABBi(Vector2i(-30, 0), Vector2i(100, 100)) to listOf(AABBi(Vector2i(0, 0), Vector2i(100, 100)), AABBi(Vector2i(2999 - 29, 0), Vector2i(2999, 100))), + AABBi(Vector2i(2900, 0), Vector2i(2950, 100)) to listOf(AABBi(Vector2i(2900, 0), Vector2i(2950, 100))), + AABBi(Vector2i(2900, 0), Vector2i(2999, 100)) to listOf(AABBi(Vector2i(2900, 0), Vector2i(2999, 100))), + AABBi(Vector2i(2900, 0), Vector2i(3000, 100)) to listOf(AABBi(Vector2i(2900, 0), Vector2i(2999, 100)), AABBi(Vector2i(0, 0), Vector2i(0, 100))), + AABBi(Vector2i(2900, 0), Vector2i(3004, 100)) to listOf(AABBi(Vector2i(2900, 0), Vector2i(2999, 100)), AABBi(Vector2i(0, 0), Vector2i(4, 100))), + ) + + for ((input, expected) in testCases) { + val split = geometry.split(input) + + if (!expected.all { split.first.contains(it) }) { + throw IllegalArgumentException("Input/expected/got:\n\t$input\n\t$expected\n\t${split.first}") + } + } + } +}