Proper chunk tracking
This commit is contained in:
parent
82b09721fc
commit
a5192bc551
@ -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.GeometryType
|
||||||
import ru.dbotthepony.kstarbound.client.gl.vertex.VertexBuilder
|
import ru.dbotthepony.kstarbound.client.gl.vertex.VertexBuilder
|
||||||
import ru.dbotthepony.kstarbound.client.input.UserInput
|
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.Camera
|
||||||
import ru.dbotthepony.kstarbound.client.render.Font
|
import ru.dbotthepony.kstarbound.client.render.Font
|
||||||
import ru.dbotthepony.kstarbound.client.render.LayeredRenderer
|
import ru.dbotthepony.kstarbound.client.render.LayeredRenderer
|
||||||
@ -947,9 +946,6 @@ class StarboundClient private constructor(val clientID: Int) : Closeable {
|
|||||||
|
|
||||||
val activeConnection = activeConnection
|
val activeConnection = activeConnection
|
||||||
|
|
||||||
if (activeConnection != null && !activeConnection.isLegacy && activeConnection.channel.isOpen)
|
|
||||||
activeConnection.send(TrackedPositionPacket(camera.pos))
|
|
||||||
|
|
||||||
uberShaderPrograms.forValidRefs { it.viewMatrix = viewportMatrixScreen }
|
uberShaderPrograms.forValidRefs { it.viewMatrix = viewportMatrixScreen }
|
||||||
fontShaderPrograms.forValidRefs { it.viewMatrix = viewportMatrixScreen }
|
fontShaderPrograms.forValidRefs { it.viewMatrix = viewportMatrixScreen }
|
||||||
|
|
||||||
|
@ -243,7 +243,7 @@ private fun materialFootstepSound(context: ExecutionContext, arguments: Argument
|
|||||||
return
|
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) {
|
private fun materialHealth(context: ExecutionContext, arguments: ArgumentIterator) {
|
||||||
|
@ -11,6 +11,7 @@ import org.apache.logging.log4j.LogManager
|
|||||||
import ru.dbotthepony.kommons.io.StreamCodec
|
import ru.dbotthepony.kommons.io.StreamCodec
|
||||||
import ru.dbotthepony.kommons.io.VarIntValueCodec
|
import ru.dbotthepony.kommons.io.VarIntValueCodec
|
||||||
import ru.dbotthepony.kommons.util.AABBi
|
import ru.dbotthepony.kommons.util.AABBi
|
||||||
|
import ru.dbotthepony.kommons.vector.Vector2d
|
||||||
import ru.dbotthepony.kommons.vector.Vector2i
|
import ru.dbotthepony.kommons.vector.Vector2i
|
||||||
import ru.dbotthepony.kstarbound.GlobalDefaults
|
import ru.dbotthepony.kstarbound.GlobalDefaults
|
||||||
import ru.dbotthepony.kstarbound.network.syncher.BasicNetworkedElement
|
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))
|
val clientStateGroup = MasterElement(NetworkedGroup(windowXMin, windowYMin, windowWidth, windowHeight, playerID, clientSpectatingEntities))
|
||||||
|
|
||||||
// in tiles
|
// in tiles
|
||||||
fun trackingRegions(): List<AABBi> {
|
fun trackingTileRegions(): List<AABBi> {
|
||||||
val result = ArrayList<AABBi>()
|
val result = ArrayList<AABBi>()
|
||||||
|
|
||||||
val mins = Vector2i(windowXMin.get() - GlobalDefaults.client.windowMonitoringBorder, windowYMin.get() - GlobalDefaults.client.windowMonitoringBorder)
|
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) {
|
if (playerEntity != null) {
|
||||||
// add an extra region the size of the window centered on the player's position to prevent nearby sectors
|
// 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
|
// 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()) {
|
for (entity in clientSpectatingEntities.get()) {
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
package ru.dbotthepony.kstarbound.network
|
package ru.dbotthepony.kstarbound.network
|
||||||
|
|
||||||
import io.netty.buffer.ByteBuf
|
import io.netty.buffer.ByteBuf
|
||||||
import io.netty.buffer.ByteBufInputStream
|
|
||||||
import io.netty.buffer.ByteBufOutputStream
|
import io.netty.buffer.ByteBufOutputStream
|
||||||
import io.netty.channel.ChannelDuplexHandler
|
import io.netty.channel.ChannelDuplexHandler
|
||||||
import io.netty.channel.ChannelHandlerContext
|
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.FindUniqueEntityPacket
|
||||||
import ru.dbotthepony.kstarbound.network.packets.serverbound.WorldClientStateUpdatePacket
|
import ru.dbotthepony.kstarbound.network.packets.serverbound.WorldClientStateUpdatePacket
|
||||||
import ru.dbotthepony.kstarbound.network.packets.serverbound.WorldStartAcknowledgePacket
|
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.BufferedInputStream
|
||||||
import java.io.DataInputStream
|
import java.io.DataInputStream
|
||||||
import java.io.DataOutputStream
|
import java.io.DataOutputStream
|
||||||
@ -358,8 +355,6 @@ class PacketRegistry(val isLegacy: Boolean) {
|
|||||||
NATIVE.add(::JoinWorldPacket)
|
NATIVE.add(::JoinWorldPacket)
|
||||||
NATIVE.add(::ChunkCellsPacket)
|
NATIVE.add(::ChunkCellsPacket)
|
||||||
NATIVE.add(::ForgetChunkPacket)
|
NATIVE.add(::ForgetChunkPacket)
|
||||||
NATIVE.add(::TrackedPositionPacket)
|
|
||||||
NATIVE.add(::TrackedSizePacket)
|
|
||||||
NATIVE.add(::SpawnWorldObjectPacket)
|
NATIVE.add(::SpawnWorldObjectPacket)
|
||||||
NATIVE.add(::ForgetEntityPacket)
|
NATIVE.add(::ForgetEntityPacket)
|
||||||
NATIVE.add(::UniverseTimeUpdatePacket)
|
NATIVE.add(::UniverseTimeUpdatePacket)
|
||||||
|
@ -4,21 +4,16 @@ import com.google.gson.JsonObject
|
|||||||
import io.netty.channel.ChannelHandlerContext
|
import io.netty.channel.ChannelHandlerContext
|
||||||
import it.unimi.dsi.fastutil.bytes.ByteArrayList
|
import it.unimi.dsi.fastutil.bytes.ByteArrayList
|
||||||
import it.unimi.dsi.fastutil.ints.Int2LongOpenHashMap
|
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.ints.Int2ObjectMaps
|
||||||
import it.unimi.dsi.fastutil.io.FastByteArrayOutputStream
|
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.ObjectLinkedOpenHashSet
|
||||||
import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet
|
import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet
|
||||||
import org.apache.logging.log4j.LogManager
|
import org.apache.logging.log4j.LogManager
|
||||||
import ru.dbotthepony.kommons.io.ByteKey
|
import ru.dbotthepony.kommons.io.ByteKey
|
||||||
import ru.dbotthepony.kommons.util.KOptional
|
import ru.dbotthepony.kommons.util.KOptional
|
||||||
import ru.dbotthepony.kommons.util.MailboxExecutorService
|
import ru.dbotthepony.kommons.vector.Vector2i
|
||||||
import ru.dbotthepony.kommons.vector.Vector2d
|
|
||||||
import ru.dbotthepony.kstarbound.client.network.packets.ForgetChunkPacket
|
|
||||||
import ru.dbotthepony.kstarbound.client.network.packets.ChunkCellsPacket
|
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.defs.WarpAlias
|
||||||
import ru.dbotthepony.kstarbound.network.Connection
|
import ru.dbotthepony.kstarbound.network.Connection
|
||||||
import ru.dbotthepony.kstarbound.network.ConnectionSide
|
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.EntityCreatePacket
|
||||||
import ru.dbotthepony.kstarbound.network.packets.EntityUpdateSetPacket
|
import ru.dbotthepony.kstarbound.network.packets.EntityUpdateSetPacket
|
||||||
import ru.dbotthepony.kstarbound.network.packets.clientbound.LegacyTileArrayUpdatePacket
|
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.PlayerWarpResultPacket
|
||||||
import ru.dbotthepony.kstarbound.network.packets.clientbound.ServerDisconnectPacket
|
import ru.dbotthepony.kstarbound.network.packets.clientbound.ServerDisconnectPacket
|
||||||
import ru.dbotthepony.kstarbound.server.world.WorldStorage
|
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.IChunkListener
|
||||||
import ru.dbotthepony.kstarbound.world.api.ImmutableCell
|
import ru.dbotthepony.kstarbound.world.api.ImmutableCell
|
||||||
import ru.dbotthepony.kstarbound.world.entities.AbstractEntity
|
import ru.dbotthepony.kstarbound.world.entities.AbstractEntity
|
||||||
import ru.dbotthepony.kstarbound.world.entities.WorldObject
|
|
||||||
import ru.dbotthepony.kstarbound.world.entities.player.PlayerEntity
|
import ru.dbotthepony.kstarbound.world.entities.player.PlayerEntity
|
||||||
import java.io.DataOutputStream
|
import java.io.DataOutputStream
|
||||||
import java.util.HashMap
|
import java.util.HashMap
|
||||||
@ -76,33 +71,6 @@ class ServerConnection(val server: StarboundServer, type: ConnectionType) : Conn
|
|||||||
return "ServerConnection[ID=$connectionID channel=$channel / $ship]"
|
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<ByteKey, KOptional<ByteArray>>()
|
private val shipChunks = HashMap<ByteKey, KOptional<ByteArray>>()
|
||||||
private val modifiedShipChunks = ObjectOpenHashSet<ByteKey>()
|
private val modifiedShipChunks = ObjectOpenHashSet<ByteKey>()
|
||||||
var shipChunkSource by Delegates.notNull<WorldStorage>()
|
var shipChunkSource by Delegates.notNull<WorldStorage>()
|
||||||
@ -123,23 +91,17 @@ class ServerConnection(val server: StarboundServer, type: ConnectionType) : Conn
|
|||||||
shipChunks.putAll(chunks)
|
shipChunks.putAll(chunks)
|
||||||
}
|
}
|
||||||
|
|
||||||
private val tickets = HashMap<ChunkPos, ServerWorld.ITicket>()
|
private val tickets = HashMap<ChunkPos, Ticket>()
|
||||||
private val pendingSend = ObjectLinkedOpenHashSet<ChunkPos>()
|
private val pendingSend = ObjectLinkedOpenHashSet<ChunkPos>()
|
||||||
|
|
||||||
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 {
|
val dirtyCells = ObjectArraySet<Vector2i>()
|
||||||
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))
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onCellChanges(x: Int, y: Int, cell: ImmutableCell) {
|
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() {
|
fun onLeaveWorld() {
|
||||||
tasks.clear()
|
tasks.clear()
|
||||||
tickets.values.forEach { it.cancel() }
|
tickets.values.forEach { it.ticket.cancel() }
|
||||||
tickets.clear()
|
tickets.clear()
|
||||||
pendingSend.clear()
|
pendingSend.clear()
|
||||||
playerEntity = null
|
playerEntity = null
|
||||||
@ -210,7 +172,7 @@ class ServerConnection(val server: StarboundServer, type: ConnectionType) : Conn
|
|||||||
flush()
|
flush()
|
||||||
}
|
}
|
||||||
|
|
||||||
tickets.values.forEach { it.cancel() }
|
tickets.values.forEach { it.ticket.cancel() }
|
||||||
tickets.clear()
|
tickets.clear()
|
||||||
pendingSend.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<ChunkPos>()
|
|
||||||
|
|
||||||
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()
|
private val entityVersions = Int2LongOpenHashMap()
|
||||||
|
|
||||||
init {
|
init {
|
||||||
@ -281,22 +208,60 @@ class ServerConnection(val server: StarboundServer, type: ConnectionType) : Conn
|
|||||||
|
|
||||||
playerEntity = world.entities[playerID.get()] as? PlayerEntity
|
playerEntity = world.entities[playerID.get()] as? PlayerEntity
|
||||||
|
|
||||||
if (needsToRecomputeTrackedChunks) {
|
run {
|
||||||
recomputeTrackedChunks()
|
val newTrackedChunks = ObjectArraySet<ChunkPos>()
|
||||||
}
|
|
||||||
|
|
||||||
val itr = pendingSend.iterator()
|
for (region in trackingTileRegions()) {
|
||||||
|
newTrackedChunks.addAll(world.geometry.tileRegion2Chunks(region))
|
||||||
for (pos in itr) {
|
|
||||||
val chunk = world.chunkMap[pos] ?: continue
|
|
||||||
|
|
||||||
if (isLegacy) {
|
|
||||||
send(LegacyTileArrayUpdatePacket(chunk))
|
|
||||||
} else {
|
|
||||||
send(ChunkCellsPacket(chunk))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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) {
|
for ((id, entity) in world.entities) {
|
||||||
|
@ -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
|
|
||||||
}
|
|
||||||
}
|
|
@ -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
|
|
||||||
}
|
|
||||||
}
|
|
@ -61,7 +61,6 @@ class ServerWorld private constructor(
|
|||||||
player.world?.removePlayer(player)
|
player.world?.removePlayer(player)
|
||||||
player.world = this
|
player.world = this
|
||||||
player.worldStartAcknowledged = false
|
player.worldStartAcknowledged = false
|
||||||
player.trackedPosition = playerSpawnPosition
|
|
||||||
|
|
||||||
if (player.isLegacy) {
|
if (player.isLegacy) {
|
||||||
val (skyData, skyVersion) = sky.networkedGroup.write(isLegacy = true)
|
val (skyData, skyVersion) = sky.networkedGroup.write(isLegacy = true)
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
package ru.dbotthepony.kstarbound.world
|
package ru.dbotthepony.kstarbound.world
|
||||||
|
|
||||||
|
import ru.dbotthepony.kommons.vector.Vector2i
|
||||||
import ru.dbotthepony.kstarbound.math.divideUp
|
import ru.dbotthepony.kstarbound.math.divideUp
|
||||||
|
|
||||||
fun positiveModulo(a: Int, b: Int): Int {
|
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: Float): Int = chunkFromCell(value.toInt())
|
||||||
fun chunkFromCell(value: Double): 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
|
// inside world bounds
|
||||||
abstract fun inBoundsCell(value: Int): Boolean
|
abstract fun inBoundsCell(value: Int): Boolean
|
||||||
abstract fun inBoundsChunk(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: Double): Double = positiveModulo(value, cells)
|
||||||
override fun cell(value: Float): Float = positiveModulo(value, cells)
|
override fun cell(value: Float): Float = positiveModulo(value, cells)
|
||||||
override fun chunk(value: Int): Int = positiveModulo(value, chunks)
|
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() {
|
class Clamper(private val cells: Int) : CoordinateMapper() {
|
||||||
@ -71,6 +121,11 @@ abstract class CoordinateMapper {
|
|||||||
return value in 0 until chunks
|
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 {
|
override fun cell(value: Int): Int {
|
||||||
return value.coerceIn(0, cells - 1)
|
return value.coerceIn(0, cells - 1)
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,10 @@
|
|||||||
package ru.dbotthepony.kstarbound.world
|
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.readVector2i
|
||||||
import ru.dbotthepony.kommons.io.writeStruct2i
|
import ru.dbotthepony.kommons.io.writeStruct2i
|
||||||
|
import ru.dbotthepony.kommons.util.AABBi
|
||||||
import ru.dbotthepony.kommons.util.IStruct2d
|
import ru.dbotthepony.kommons.util.IStruct2d
|
||||||
import ru.dbotthepony.kommons.util.IStruct2f
|
import ru.dbotthepony.kommons.util.IStruct2f
|
||||||
import ru.dbotthepony.kommons.util.IStruct2i
|
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()
|
if (x == pos.x && y == pos.y) return pos.toLong()
|
||||||
return ChunkPos.toLong(x, y)
|
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<List<AABBi>, 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<ChunkPos> {
|
||||||
|
if (region.mins == region.maxs)
|
||||||
|
return emptySet()
|
||||||
|
|
||||||
|
val result = ObjectArraySet<ChunkPos>()
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -21,7 +21,7 @@ sealed class AbstractCell {
|
|||||||
abstract fun immutable(): ImmutableCell
|
abstract fun immutable(): ImmutableCell
|
||||||
abstract fun mutable(): MutableCell
|
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)
|
return LegacyNetworkCellState(background.toLegacyNet(), foreground.toLegacyNet(), foreground.material.value.collisionKind, biome, envBiome, liquid.toLegacyNet(), dungeonId)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -29,7 +29,7 @@ sealed class AbstractTileState {
|
|||||||
return (modifierHueShift / 360f * 255).toInt()
|
return (modifierHueShift / 360f * 255).toInt()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun toLegacyNet(): LegacyNetworkTileState {
|
open fun toLegacyNet(): LegacyNetworkTileState {
|
||||||
if (material.id != null && material.id in 0 .. 65535) {
|
if (material.id != null && material.id in 0 .. 65535) {
|
||||||
val validMod = modifier?.id != null && modifier!!.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)
|
return LegacyNetworkTileState(material.id!!, byteHueShift(), color.ordinal, if (validMod) modifier!!.id!! else 0, if (validMod) byteModifierHueShift() else 0)
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
package ru.dbotthepony.kstarbound.world.api
|
package ru.dbotthepony.kstarbound.world.api
|
||||||
|
|
||||||
|
import ru.dbotthepony.kstarbound.network.LegacyNetworkCellState
|
||||||
|
|
||||||
data class ImmutableCell(
|
data class ImmutableCell(
|
||||||
override val foreground: ImmutableTileState = AbstractTileState.EMPTY,
|
override val foreground: ImmutableTileState = AbstractTileState.EMPTY,
|
||||||
override val background: ImmutableTileState = AbstractTileState.EMPTY,
|
override val background: ImmutableTileState = AbstractTileState.EMPTY,
|
||||||
@ -14,6 +16,12 @@ data class ImmutableCell(
|
|||||||
return this
|
return this
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private val legacyNet by lazy { super.toLegacyNet() }
|
||||||
|
|
||||||
|
override fun toLegacyNet(): LegacyNetworkCellState {
|
||||||
|
return legacyNet
|
||||||
|
}
|
||||||
|
|
||||||
override fun mutable(): MutableCell {
|
override fun mutable(): MutableCell {
|
||||||
return MutableCell(foreground.mutable(), background.mutable(), liquid.mutable(), dungeonId, biome, envBiome, isIndestructible)
|
return MutableCell(foreground.mutable(), background.mutable(), liquid.mutable(), dungeonId, biome, envBiome, isIndestructible)
|
||||||
}
|
}
|
||||||
|
@ -4,6 +4,7 @@ import ru.dbotthepony.kstarbound.Registry
|
|||||||
import ru.dbotthepony.kstarbound.defs.tile.BuiltinMetaMaterials
|
import ru.dbotthepony.kstarbound.defs.tile.BuiltinMetaMaterials
|
||||||
import ru.dbotthepony.kstarbound.defs.tile.MaterialModifier
|
import ru.dbotthepony.kstarbound.defs.tile.MaterialModifier
|
||||||
import ru.dbotthepony.kstarbound.defs.tile.TileDefinition
|
import ru.dbotthepony.kstarbound.defs.tile.TileDefinition
|
||||||
|
import ru.dbotthepony.kstarbound.network.LegacyNetworkTileState
|
||||||
|
|
||||||
data class ImmutableTileState(
|
data class ImmutableTileState(
|
||||||
override var material: Registry.Entry<TileDefinition> = BuiltinMetaMaterials.NULL,
|
override var material: Registry.Entry<TileDefinition> = BuiltinMetaMaterials.NULL,
|
||||||
@ -16,6 +17,12 @@ data class ImmutableTileState(
|
|||||||
return this
|
return this
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private val legacyNet by lazy { super.toLegacyNet() }
|
||||||
|
|
||||||
|
override fun toLegacyNet(): LegacyNetworkTileState {
|
||||||
|
return legacyNet
|
||||||
|
}
|
||||||
|
|
||||||
override fun mutable(): MutableTileState {
|
override fun mutable(): MutableTileState {
|
||||||
return MutableTileState(material, modifier, color, hueShift, modifierHueShift)
|
return MutableTileState(material, modifier, color, hueShift, modifierHueShift)
|
||||||
}
|
}
|
||||||
|
35
src/test/kotlin/ru/dbotthepony/kstarbound/test/WorldTests.kt
Normal file
35
src/test/kotlin/ru/dbotthepony/kstarbound/test/WorldTests.kt
Normal file
@ -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}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user