Persistent universe parameters storage, as well as player context
This commit is contained in:
parent
c016dade54
commit
195de2d160
@ -73,6 +73,7 @@ import ru.dbotthepony.kstarbound.util.HashTableInterner
|
|||||||
import ru.dbotthepony.kstarbound.util.random.AbstractPerlinNoise
|
import ru.dbotthepony.kstarbound.util.random.AbstractPerlinNoise
|
||||||
import ru.dbotthepony.kstarbound.util.random.nextRange
|
import ru.dbotthepony.kstarbound.util.random.nextRange
|
||||||
import ru.dbotthepony.kstarbound.util.random.random
|
import ru.dbotthepony.kstarbound.util.random.random
|
||||||
|
import ru.dbotthepony.kstarbound.world.SystemWorldLocation
|
||||||
import ru.dbotthepony.kstarbound.world.physics.Poly
|
import ru.dbotthepony.kstarbound.world.physics.Poly
|
||||||
import java.io.*
|
import java.io.*
|
||||||
import java.lang.ref.Cleaner
|
import java.lang.ref.Cleaner
|
||||||
@ -368,6 +369,8 @@ object Starbound : BlockableEventLoop("Universe Thread"), Scheduler, ISBFileLoca
|
|||||||
registerTypeAdapterFactory(BiomePlacementItemType.DEFINITION_ADAPTER)
|
registerTypeAdapterFactory(BiomePlacementItemType.DEFINITION_ADAPTER)
|
||||||
registerTypeAdapterFactory(BiomePlaceables.Item.Companion)
|
registerTypeAdapterFactory(BiomePlaceables.Item.Companion)
|
||||||
|
|
||||||
|
registerTypeAdapterFactory(SystemWorldLocation.ADAPTER)
|
||||||
|
|
||||||
// register companion first, so it has lesser priority than dispatching adapter
|
// register companion first, so it has lesser priority than dispatching adapter
|
||||||
registerTypeAdapterFactory(VisitableWorldParametersType.Companion)
|
registerTypeAdapterFactory(VisitableWorldParametersType.Companion)
|
||||||
registerTypeAdapterFactory(VisitableWorldParametersType.ADAPTER)
|
registerTypeAdapterFactory(VisitableWorldParametersType.ADAPTER)
|
||||||
|
@ -5,6 +5,7 @@ import com.google.gson.TypeAdapter
|
|||||||
import com.google.gson.annotations.JsonAdapter
|
import com.google.gson.annotations.JsonAdapter
|
||||||
import com.google.gson.stream.JsonReader
|
import com.google.gson.stream.JsonReader
|
||||||
import com.google.gson.stream.JsonWriter
|
import com.google.gson.stream.JsonWriter
|
||||||
|
import kotlinx.coroutines.future.await
|
||||||
import ru.dbotthepony.kommons.io.StreamCodec
|
import ru.dbotthepony.kommons.io.StreamCodec
|
||||||
import ru.dbotthepony.kommons.io.readUUID
|
import ru.dbotthepony.kommons.io.readUUID
|
||||||
import ru.dbotthepony.kstarbound.io.readVector2d
|
import ru.dbotthepony.kstarbound.io.readVector2d
|
||||||
@ -13,16 +14,20 @@ import ru.dbotthepony.kommons.io.writeBinaryString
|
|||||||
import ru.dbotthepony.kommons.io.writeStruct2d
|
import ru.dbotthepony.kommons.io.writeStruct2d
|
||||||
import ru.dbotthepony.kommons.io.writeStruct2f
|
import ru.dbotthepony.kommons.io.writeStruct2f
|
||||||
import ru.dbotthepony.kommons.io.writeUUID
|
import ru.dbotthepony.kommons.io.writeUUID
|
||||||
|
import ru.dbotthepony.kstarbound.Globals
|
||||||
import ru.dbotthepony.kstarbound.math.vector.Vector2d
|
import ru.dbotthepony.kstarbound.math.vector.Vector2d
|
||||||
import ru.dbotthepony.kstarbound.io.readInternedString
|
import ru.dbotthepony.kstarbound.io.readInternedString
|
||||||
import ru.dbotthepony.kstarbound.json.builder.IStringSerializable
|
import ru.dbotthepony.kstarbound.json.builder.IStringSerializable
|
||||||
|
import ru.dbotthepony.kstarbound.math.AABB
|
||||||
import ru.dbotthepony.kstarbound.network.syncher.legacyCodec
|
import ru.dbotthepony.kstarbound.network.syncher.legacyCodec
|
||||||
import ru.dbotthepony.kstarbound.network.syncher.nativeCodec
|
import ru.dbotthepony.kstarbound.network.syncher.nativeCodec
|
||||||
import ru.dbotthepony.kstarbound.server.ServerConnection
|
import ru.dbotthepony.kstarbound.server.ServerConnection
|
||||||
|
import ru.dbotthepony.kstarbound.server.world.ServerChunk
|
||||||
import ru.dbotthepony.kstarbound.server.world.ServerWorld
|
import ru.dbotthepony.kstarbound.server.world.ServerWorld
|
||||||
import java.io.DataInputStream
|
import java.io.DataInputStream
|
||||||
import java.io.DataOutputStream
|
import java.io.DataOutputStream
|
||||||
import java.util.UUID
|
import java.util.UUID
|
||||||
|
import kotlin.math.PI
|
||||||
import kotlin.math.roundToInt
|
import kotlin.math.roundToInt
|
||||||
|
|
||||||
// original game has MVariant here
|
// original game has MVariant here
|
||||||
@ -35,14 +40,14 @@ import kotlin.math.roundToInt
|
|||||||
@JsonAdapter(SpawnTarget.Adapter::class)
|
@JsonAdapter(SpawnTarget.Adapter::class)
|
||||||
sealed class SpawnTarget {
|
sealed class SpawnTarget {
|
||||||
abstract fun write(stream: DataOutputStream, isLegacy: Boolean)
|
abstract fun write(stream: DataOutputStream, isLegacy: Boolean)
|
||||||
abstract fun resolve(world: ServerWorld): Vector2d?
|
abstract suspend fun resolve(world: ServerWorld): Vector2d?
|
||||||
|
|
||||||
object Whatever : SpawnTarget() {
|
object Whatever : SpawnTarget() {
|
||||||
override fun write(stream: DataOutputStream, isLegacy: Boolean) {
|
override fun write(stream: DataOutputStream, isLegacy: Boolean) {
|
||||||
stream.writeByte(0)
|
stream.writeByte(0)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun resolve(world: ServerWorld): Vector2d {
|
override suspend fun resolve(world: ServerWorld): Vector2d {
|
||||||
return world.playerSpawnPosition
|
return world.playerSpawnPosition
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -57,7 +62,7 @@ sealed class SpawnTarget {
|
|||||||
stream.writeBinaryString(id)
|
stream.writeBinaryString(id)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun resolve(world: ServerWorld): Vector2d? {
|
override suspend fun resolve(world: ServerWorld): Vector2d? {
|
||||||
return world.entities.values.firstOrNull { it.uniqueID.get().orNull() == id }?.position
|
return world.entities.values.firstOrNull { it.uniqueID.get().orNull() == id }?.position
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -81,7 +86,7 @@ sealed class SpawnTarget {
|
|||||||
return "${position.x.roundToInt()}.${position.y.roundToInt()}"
|
return "${position.x.roundToInt()}.${position.y.roundToInt()}"
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun resolve(world: ServerWorld): Vector2d {
|
override suspend fun resolve(world: ServerWorld): Vector2d {
|
||||||
return position
|
return position
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -101,8 +106,26 @@ sealed class SpawnTarget {
|
|||||||
return position.roundToInt().toString()
|
return position.roundToInt().toString()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun resolve(world: ServerWorld): Vector2d {
|
override suspend fun resolve(world: ServerWorld): Vector2d {
|
||||||
TODO("Not yet implemented")
|
val basePos = Vector2d(position, world.geometry.size.y * 0.5)
|
||||||
|
val tickets = ArrayList<ServerChunk.ITicket>()
|
||||||
|
|
||||||
|
try {
|
||||||
|
for (i in 0 until Globals.worldServer.playerSpaceStartMaximumTries) {
|
||||||
|
val testPos = world.geometry.wrap(basePos + Vector2d.angle(world.random.nextDouble(PI * 2.0), i * Globals.worldServer.playerSpaceStartDistanceIncrement))
|
||||||
|
val testRect = AABB.withSide(testPos, Globals.worldServer.playerSpaceStartRegionSize.x, Globals.worldServer.playerSpaceStartRegionSize.y)
|
||||||
|
|
||||||
|
tickets.addAll(world.permanentChunkTicket(testRect).await())
|
||||||
|
tickets.forEach { it.chunk.await() }
|
||||||
|
|
||||||
|
if (!world.anyCellSatisfies(testRect) { x, y, cell -> cell.foreground.material.value.collisionKind.isSolidCollision })
|
||||||
|
return testPos
|
||||||
|
}
|
||||||
|
|
||||||
|
return basePos
|
||||||
|
} finally {
|
||||||
|
tickets.forEach { it.cancel() }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -134,7 +157,8 @@ sealed class SpawnTarget {
|
|||||||
val matchPos = position.matchEntire(value)
|
val matchPos = position.matchEntire(value)
|
||||||
|
|
||||||
if (matchPos != null) {
|
if (matchPos != null) {
|
||||||
return Position(Vector2d(matchPos.groups[0]!!.value.toDouble(), matchPos.groups[0]!!.value.toDouble()))
|
val split = matchPos.groups[0]!!.value.split('.')
|
||||||
|
return Position(Vector2d(split[0].toDouble(), split[1].toDouble()))
|
||||||
}
|
}
|
||||||
|
|
||||||
val matchX = positionX.matchEntire(value)
|
val matchX = positionX.matchEntire(value)
|
||||||
|
@ -11,6 +11,7 @@ import java.util.function.Predicate
|
|||||||
data class UniverseServerConfig(
|
data class UniverseServerConfig(
|
||||||
// in milliseconds
|
// in milliseconds
|
||||||
val clockUpdatePacketInterval: Long = 500L,
|
val clockUpdatePacketInterval: Long = 500L,
|
||||||
|
val universeStorageInterval: Long = 10000L,
|
||||||
val findStarterWorldParameters: StarterWorld,
|
val findStarterWorldParameters: StarterWorld,
|
||||||
val queuedFlightWaitTime: Double = 0.0,
|
val queuedFlightWaitTime: Double = 0.0,
|
||||||
|
|
||||||
|
@ -96,12 +96,10 @@ sealed class WorldID {
|
|||||||
if (value.isBlank())
|
if (value.isBlank())
|
||||||
return Limbo
|
return Limbo
|
||||||
|
|
||||||
val parts = value.split(':')
|
return when (val type = value.substringBefore(':').lowercase()) {
|
||||||
|
|
||||||
return when (val type = parts[0].lowercase()) {
|
|
||||||
"nowhere" -> Limbo
|
"nowhere" -> Limbo
|
||||||
"instanceworld" -> {
|
"instanceworld" -> {
|
||||||
val rest = parts[1].split(':')
|
val rest = value.substringAfter(':').split(':')
|
||||||
|
|
||||||
if (rest.isEmpty() || rest.size > 3) {
|
if (rest.isEmpty() || rest.size > 3) {
|
||||||
throw IllegalArgumentException("Malformed InstanceWorld string: $value")
|
throw IllegalArgumentException("Malformed InstanceWorld string: $value")
|
||||||
@ -125,8 +123,8 @@ sealed class WorldID {
|
|||||||
Instance(name, uuid, threatLevel)
|
Instance(name, uuid, threatLevel)
|
||||||
}
|
}
|
||||||
|
|
||||||
"celestialworld" -> Celestial(UniversePos.parse(parts[1]))
|
"celestialworld" -> Celestial(UniversePos.parse(value.substringAfter(':')))
|
||||||
"clientshipworld" -> ShipWorld(uuidFromStarboundString(parts[1]))
|
"clientshipworld" -> ShipWorld(uuidFromStarboundString(value.substringAfter(':')))
|
||||||
else -> throw IllegalArgumentException("Invalid WorldID type: $type (input: $value)")
|
else -> throw IllegalArgumentException("Invalid WorldID type: $type (input: $value)")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -4,6 +4,9 @@ import ru.dbotthepony.kstarbound.math.vector.Vector2d
|
|||||||
import ru.dbotthepony.kstarbound.math.vector.Vector2i
|
import ru.dbotthepony.kstarbound.math.vector.Vector2i
|
||||||
|
|
||||||
class WorldServerConfig(
|
class WorldServerConfig(
|
||||||
|
val playerSpaceStartRegionSize: Vector2d,
|
||||||
|
val playerSpaceStartDistanceIncrement: Double,
|
||||||
|
val playerSpaceStartMaximumTries: Int,
|
||||||
val playerStartRegionMaximumTries: Int = 1,
|
val playerStartRegionMaximumTries: Int = 1,
|
||||||
val playerStartRegionMaximumVerticalSearch: Int = 1,
|
val playerStartRegionMaximumVerticalSearch: Int = 1,
|
||||||
val playerStartRegionSize: Vector2d,
|
val playerStartRegionSize: Vector2d,
|
||||||
|
@ -9,6 +9,7 @@ import com.google.gson.JsonPrimitive
|
|||||||
import com.google.gson.JsonSyntaxException
|
import com.google.gson.JsonSyntaxException
|
||||||
import com.google.gson.stream.JsonReader
|
import com.google.gson.stream.JsonReader
|
||||||
import com.google.gson.stream.JsonToken
|
import com.google.gson.stream.JsonToken
|
||||||
|
import it.unimi.dsi.fastutil.io.FastByteArrayInputStream
|
||||||
import ru.dbotthepony.kommons.io.readBinaryString
|
import ru.dbotthepony.kommons.io.readBinaryString
|
||||||
import ru.dbotthepony.kommons.io.readSignedVarLong
|
import ru.dbotthepony.kommons.io.readSignedVarLong
|
||||||
import ru.dbotthepony.kommons.io.readString
|
import ru.dbotthepony.kommons.io.readString
|
||||||
@ -21,6 +22,10 @@ import java.io.InputStream
|
|||||||
import java.io.Reader
|
import java.io.Reader
|
||||||
import java.util.LinkedList
|
import java.util.LinkedList
|
||||||
|
|
||||||
|
fun ByteArray.readJsonElement(): JsonElement = DataInputStream(FastByteArrayInputStream(this)).readJsonElement()
|
||||||
|
fun ByteArray.readJsonObject(): JsonObject = DataInputStream(FastByteArrayInputStream(this)).readJsonObject()
|
||||||
|
fun ByteArray.readJsonArray(): JsonArray = DataInputStream(FastByteArrayInputStream(this)).readJsonArray()
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Позволяет читать двоичный JSON прямиком в [JsonElement]
|
* Позволяет читать двоичный JSON прямиком в [JsonElement]
|
||||||
*/
|
*/
|
||||||
|
@ -5,12 +5,19 @@ import com.google.gson.JsonElement
|
|||||||
import com.google.gson.JsonNull
|
import com.google.gson.JsonNull
|
||||||
import com.google.gson.JsonObject
|
import com.google.gson.JsonObject
|
||||||
import com.google.gson.JsonPrimitive
|
import com.google.gson.JsonPrimitive
|
||||||
|
import it.unimi.dsi.fastutil.io.FastByteArrayInputStream
|
||||||
|
import it.unimi.dsi.fastutil.io.FastByteArrayOutputStream
|
||||||
import ru.dbotthepony.kommons.io.writeBinaryString
|
import ru.dbotthepony.kommons.io.writeBinaryString
|
||||||
import ru.dbotthepony.kommons.io.writeSignedVarLong
|
import ru.dbotthepony.kommons.io.writeSignedVarLong
|
||||||
import ru.dbotthepony.kommons.io.writeVarInt
|
import ru.dbotthepony.kommons.io.writeVarInt
|
||||||
|
import java.io.DataInputStream
|
||||||
import java.io.DataOutputStream
|
import java.io.DataOutputStream
|
||||||
import kotlin.math.absoluteValue
|
import kotlin.math.absoluteValue
|
||||||
|
|
||||||
|
fun JsonElement.writeJsonElement(): ByteArray = FastByteArrayOutputStream().let { DataOutputStream(it).writeJsonElement(this); it.array.copyOf(it.length) }
|
||||||
|
fun JsonObject.writeJsonObject(): ByteArray = FastByteArrayOutputStream().let { DataOutputStream(it).writeJsonObject(this); it.array.copyOf(it.length) }
|
||||||
|
fun JsonArray.writeJsonArray(): ByteArray = FastByteArrayOutputStream().let { DataOutputStream(it).writeJsonArray(this); it.array.copyOf(it.length) }
|
||||||
|
|
||||||
fun DataOutputStream.writeJsonElement(value: JsonElement) {
|
fun DataOutputStream.writeJsonElement(value: JsonElement) {
|
||||||
when (value) {
|
when (value) {
|
||||||
is JsonNull -> write(BinaryJsonReader.TYPE_NULL)
|
is JsonNull -> write(BinaryJsonReader.TYPE_NULL)
|
||||||
|
@ -15,6 +15,7 @@ import ru.dbotthepony.kommons.util.KOptional
|
|||||||
import ru.dbotthepony.kstarbound.math.vector.Vector2i
|
import ru.dbotthepony.kstarbound.math.vector.Vector2i
|
||||||
import ru.dbotthepony.kstarbound.math.vector.Vector3i
|
import ru.dbotthepony.kstarbound.math.vector.Vector3i
|
||||||
import ru.dbotthepony.kstarbound.Globals
|
import ru.dbotthepony.kstarbound.Globals
|
||||||
|
import ru.dbotthepony.kstarbound.Starbound
|
||||||
import ru.dbotthepony.kstarbound.defs.WarpAction
|
import ru.dbotthepony.kstarbound.defs.WarpAction
|
||||||
import ru.dbotthepony.kstarbound.defs.WarpAlias
|
import ru.dbotthepony.kstarbound.defs.WarpAlias
|
||||||
import ru.dbotthepony.kstarbound.defs.WarpMode
|
import ru.dbotthepony.kstarbound.defs.WarpMode
|
||||||
@ -22,6 +23,7 @@ import ru.dbotthepony.kstarbound.defs.WorldID
|
|||||||
import ru.dbotthepony.kstarbound.defs.world.CelestialParameters
|
import ru.dbotthepony.kstarbound.defs.world.CelestialParameters
|
||||||
import ru.dbotthepony.kstarbound.defs.world.SkyType
|
import ru.dbotthepony.kstarbound.defs.world.SkyType
|
||||||
import ru.dbotthepony.kstarbound.defs.world.VisitableWorldParameters
|
import ru.dbotthepony.kstarbound.defs.world.VisitableWorldParameters
|
||||||
|
import ru.dbotthepony.kstarbound.json.builder.JsonFactory
|
||||||
import ru.dbotthepony.kstarbound.network.Connection
|
import ru.dbotthepony.kstarbound.network.Connection
|
||||||
import ru.dbotthepony.kstarbound.network.ConnectionSide
|
import ru.dbotthepony.kstarbound.network.ConnectionSide
|
||||||
import ru.dbotthepony.kstarbound.network.ConnectionType
|
import ru.dbotthepony.kstarbound.network.ConnectionType
|
||||||
@ -33,13 +35,14 @@ import ru.dbotthepony.kstarbound.network.packets.clientbound.ServerDisconnectPac
|
|||||||
import ru.dbotthepony.kstarbound.server.world.ServerWorldTracker
|
import ru.dbotthepony.kstarbound.server.world.ServerWorldTracker
|
||||||
import ru.dbotthepony.kstarbound.server.world.WorldStorage
|
import ru.dbotthepony.kstarbound.server.world.WorldStorage
|
||||||
import ru.dbotthepony.kstarbound.server.world.LegacyWorldStorage
|
import ru.dbotthepony.kstarbound.server.world.LegacyWorldStorage
|
||||||
import ru.dbotthepony.kstarbound.server.world.NativeWorldStorage
|
|
||||||
import ru.dbotthepony.kstarbound.server.world.ServerSystemWorld
|
import ru.dbotthepony.kstarbound.server.world.ServerSystemWorld
|
||||||
import ru.dbotthepony.kstarbound.server.world.ServerWorld
|
import ru.dbotthepony.kstarbound.server.world.ServerWorld
|
||||||
import ru.dbotthepony.kstarbound.world.SystemWorldLocation
|
import ru.dbotthepony.kstarbound.world.SystemWorldLocation
|
||||||
import ru.dbotthepony.kstarbound.world.UniversePos
|
import ru.dbotthepony.kstarbound.world.UniversePos
|
||||||
import java.util.HashMap
|
import java.util.HashMap
|
||||||
import java.util.UUID
|
import java.util.UUID
|
||||||
|
import java.util.concurrent.Future
|
||||||
|
import java.util.concurrent.TimeUnit
|
||||||
import kotlin.properties.Delegates
|
import kotlin.properties.Delegates
|
||||||
|
|
||||||
// serverside part of connection
|
// serverside part of connection
|
||||||
@ -108,15 +111,23 @@ class ServerConnection(val server: StarboundServer, type: ConnectionType) : Conn
|
|||||||
}
|
}
|
||||||
|
|
||||||
private var remoteVersion = 0L
|
private var remoteVersion = 0L
|
||||||
|
private var saveClientContextTask: Future<*>? = null
|
||||||
|
|
||||||
override fun onChannelClosed() {
|
override fun onChannelClosed() {
|
||||||
playerEntity = null
|
playerEntity = null
|
||||||
|
|
||||||
|
saveClientContextTask?.cancel(false)
|
||||||
|
tracker?.remove("Connection channel closed")
|
||||||
|
tracker = null
|
||||||
|
|
||||||
|
saveClientContext()
|
||||||
super.onChannelClosed()
|
super.onChannelClosed()
|
||||||
|
|
||||||
warpQueue.close()
|
warpQueue.close()
|
||||||
server.channels.freeConnectionID(connectionID)
|
server.channels.freeConnectionID(connectionID)
|
||||||
server.channels.connections.remove(this)
|
server.channels.connections.remove(this)
|
||||||
server.freeNickname(nickname)
|
server.freeNickname(nickname)
|
||||||
|
|
||||||
systemWorld?.removeClient(this)
|
systemWorld?.removeClient(this)
|
||||||
systemWorld = null
|
systemWorld = null
|
||||||
|
|
||||||
@ -132,15 +143,19 @@ class ServerConnection(val server: StarboundServer, type: ConnectionType) : Conn
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private val warpQueue = Channel<Pair<WarpAction, Boolean>>(capacity = 10)
|
private data class WarpRequest(val action: WarpAction, val deploy: Boolean, val ifFailed: WarpAction?)
|
||||||
|
private val warpQueue = Channel<WarpRequest>(capacity = 10)
|
||||||
|
|
||||||
private suspend fun warpEventLoop() {
|
private suspend fun warpEventLoop() {
|
||||||
while (true) {
|
while (true) {
|
||||||
var (request, deploy) = warpQueue.receive()
|
var (request, deploy, ifFailed) = warpQueue.receive()
|
||||||
|
|
||||||
if (request is WarpAlias)
|
if (request is WarpAlias)
|
||||||
request = request.remap(this)
|
request = request.remap(this)
|
||||||
|
|
||||||
|
if (ifFailed is WarpAlias)
|
||||||
|
ifFailed = ifFailed.remap(this)
|
||||||
|
|
||||||
LOGGER.info("Trying to warp ${alias()} to $request")
|
LOGGER.info("Trying to warp ${alias()} to $request")
|
||||||
|
|
||||||
val resolve = request.resolve(this)
|
val resolve = request.resolve(this)
|
||||||
@ -155,7 +170,12 @@ class ServerConnection(val server: StarboundServer, type: ConnectionType) : Conn
|
|||||||
server.loadWorld(resolve).await()
|
server.loadWorld(resolve).await()
|
||||||
} catch (err: Throwable) {
|
} catch (err: Throwable) {
|
||||||
send(PlayerWarpResultPacket(false, request, false))
|
send(PlayerWarpResultPacket(false, request, false))
|
||||||
LOGGER.error("Unable to wark ${alias()} to $request", err)
|
LOGGER.error("Unable to warp ${alias()} to $request", err)
|
||||||
|
|
||||||
|
if (ifFailed != null) {
|
||||||
|
enqueueWarp(ifFailed)
|
||||||
|
}
|
||||||
|
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -192,8 +212,7 @@ class ServerConnection(val server: StarboundServer, type: ConnectionType) : Conn
|
|||||||
|
|
||||||
private var currentOrbitalWarpAction = KOptional<Pair<WarpAction, WarpMode>>()
|
private var currentOrbitalWarpAction = KOptional<Pair<WarpAction, WarpMode>>()
|
||||||
|
|
||||||
// coordinates ship flight
|
private suspend fun findStartingSystem(): UniversePos? {
|
||||||
private suspend fun shipFlightEventLoop() {
|
|
||||||
shipWorld.sky.skyType = SkyType.ORBITAL
|
shipWorld.sky.skyType = SkyType.ORBITAL
|
||||||
shipWorld.sky.startFlying(true, true)
|
shipWorld.sky.startFlying(true, true)
|
||||||
var visited = 0
|
var visited = 0
|
||||||
@ -235,25 +254,46 @@ class ServerConnection(val server: StarboundServer, type: ConnectionType) : Conn
|
|||||||
if (found == null) {
|
if (found == null) {
|
||||||
LOGGER.fatal("Unable to find starter world for $this!")
|
LOGGER.fatal("Unable to find starter world for $this!")
|
||||||
disconnect("Unable to find starter world")
|
disconnect("Unable to find starter world")
|
||||||
return
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
LOGGER.info("Found appropriate starter world at $found for ${alias()}")
|
LOGGER.info("Found appropriate starter world at $found for ${alias()}")
|
||||||
|
return found
|
||||||
|
}
|
||||||
|
|
||||||
val worldPromise = server.loadSystemWorld(found.location)
|
private var systemWorldLocation: SystemWorldLocation = SystemWorldLocation.Transit
|
||||||
|
|
||||||
worldPromise.thenApply {
|
// coordinates ship flight
|
||||||
systemWorld = it
|
private suspend fun shipFlightEventLoop(initialLocation: Vector3i, inWorldLocation: SystemWorldLocation) {
|
||||||
|
val worldPromise = server.loadSystemWorld(initialLocation)
|
||||||
|
val loadWorld = worldPromise.await()
|
||||||
|
var actualInWorldLocation = inWorldLocation
|
||||||
|
|
||||||
if (!isConnected) {
|
var world: ServerSystemWorld
|
||||||
it.removeClient(this)
|
|
||||||
|
if (loadWorld == null) {
|
||||||
|
LOGGER.warn("Tried to put player to system world at $initialLocation, but we are unable to load it")
|
||||||
|
// we ended up nowhere, try to find new starter location
|
||||||
|
val find = findStartingSystem() ?: return
|
||||||
|
val tryAnother = server.loadSystemWorld(find.location).await()
|
||||||
|
|
||||||
|
if (tryAnother == null) {
|
||||||
|
// how?
|
||||||
|
disconnect("Unable to put player in system world")
|
||||||
|
return
|
||||||
|
} else {
|
||||||
|
actualInWorldLocation = SystemWorldLocation.Celestial(find)
|
||||||
|
world = tryAnother
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
world = loadWorld
|
||||||
}
|
}
|
||||||
|
|
||||||
var world = worldPromise.await()
|
this.systemWorld = world
|
||||||
var ship = world.addClient(this, location = SystemWorldLocation.Celestial(found)).await()
|
var ship = world.addClient(this, location = actualInWorldLocation).await()
|
||||||
shipWorld.sky.stopFlyingAt(ship.location.skyParameters(world))
|
shipWorld.sky.stopFlyingAt(ship.location.skyParameters(world))
|
||||||
shipCoordinate = found
|
shipCoordinate = UniversePos(world.location)
|
||||||
|
systemWorldLocation = actualInWorldLocation
|
||||||
|
|
||||||
run {
|
run {
|
||||||
val action = ship.location.orbitalAction(world)
|
val action = ship.location.orbitalAction(world)
|
||||||
@ -280,6 +320,7 @@ class ServerConnection(val server: StarboundServer, type: ConnectionType) : Conn
|
|||||||
}
|
}
|
||||||
|
|
||||||
currentFlightJob = scope.launch {
|
currentFlightJob = scope.launch {
|
||||||
|
systemWorldLocation = location
|
||||||
val coords = flight.await()
|
val coords = flight.await()
|
||||||
val action = coords.orbitalAction(world)
|
val action = coords.orbitalAction(world)
|
||||||
currentOrbitalWarpAction = action
|
currentOrbitalWarpAction = action
|
||||||
@ -315,24 +356,16 @@ class ServerConnection(val server: StarboundServer, type: ConnectionType) : Conn
|
|||||||
}
|
}
|
||||||
|
|
||||||
LOGGER.info("${alias()} is flying to new system: ${UniversePos(system)}")
|
LOGGER.info("${alias()} is flying to new system: ${UniversePos(system)}")
|
||||||
val newSystem = server.loadSystemWorld(system)
|
|
||||||
|
|
||||||
shipCoordinate = UniversePos(system)
|
|
||||||
currentOrbitalWarpAction = KOptional()
|
currentOrbitalWarpAction = KOptional()
|
||||||
|
|
||||||
for (client in shipWorld.clients) {
|
for (client in shipWorld.clients) {
|
||||||
client.client.orbitalWarpAction = KOptional()
|
client.client.orbitalWarpAction = KOptional()
|
||||||
}
|
}
|
||||||
|
|
||||||
newSystem.thenApply {
|
world = server.loadSystemWorld(system).await() ?: world
|
||||||
systemWorld = it
|
shipCoordinate = UniversePos(world.location) // update ship coordinate after we have successfully travelled to destination
|
||||||
|
this.systemWorld = world
|
||||||
if (!isConnected) {
|
|
||||||
it.removeClient(this)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
world = newSystem.await()
|
|
||||||
ship = world.addClient(this).await()
|
ship = world.addClient(this).await()
|
||||||
|
|
||||||
val newParams = ship.location.skyParameters(world)
|
val newParams = ship.location.skyParameters(world)
|
||||||
@ -400,8 +433,8 @@ class ServerConnection(val server: StarboundServer, type: ConnectionType) : Conn
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun enqueueWarp(destination: WarpAction, deploy: Boolean = false) {
|
fun enqueueWarp(destination: WarpAction, deploy: Boolean = false, ifFailed: WarpAction? = null) {
|
||||||
warpQueue.trySend(destination to deploy)
|
warpQueue.trySend(WarpRequest(destination, deploy, ifFailed))
|
||||||
}
|
}
|
||||||
|
|
||||||
fun tick() {
|
fun tick() {
|
||||||
@ -445,12 +478,10 @@ class ServerConnection(val server: StarboundServer, type: ConnectionType) : Conn
|
|||||||
}
|
}
|
||||||
|
|
||||||
isReady = false
|
isReady = false
|
||||||
tracker?.remove()
|
|
||||||
tracker = null
|
|
||||||
|
|
||||||
if (::shipWorld.isInitialized) {
|
tracker?.remove("Disconnect")
|
||||||
shipWorld.eventLoop.shutdown()
|
tracker = null
|
||||||
}
|
saveClientContext()
|
||||||
|
|
||||||
if (channel.isOpen) {
|
if (channel.isOpen) {
|
||||||
// say goodbye
|
// say goodbye
|
||||||
@ -479,6 +510,55 @@ class ServerConnection(val server: StarboundServer, type: ConnectionType) : Conn
|
|||||||
|
|
||||||
private var countedTowardsPlayerCount = false
|
private var countedTowardsPlayerCount = false
|
||||||
|
|
||||||
|
@JsonFactory
|
||||||
|
data class ClientContextData(
|
||||||
|
val shipCoordinate: Vector3i,
|
||||||
|
val systemLocation: SystemWorldLocation,
|
||||||
|
val returnWarp: WarpAction? = null
|
||||||
|
)
|
||||||
|
|
||||||
|
fun saveClientContext() {
|
||||||
|
if (server.isShutdown && !server.isSameThread())
|
||||||
|
return
|
||||||
|
|
||||||
|
val data = ClientContextData(shipCoordinate.location, systemWorldLocation, returnWarp)
|
||||||
|
server.writeClientContext(uuid!!, Starbound.gson.toJsonTree(data) as JsonObject)
|
||||||
|
}
|
||||||
|
|
||||||
|
private suspend fun loadDataAndDispatchEventLoops() {
|
||||||
|
val context = try {
|
||||||
|
Starbound.gson.fromJson(server.loadClientContext(uuid!!).await(), ClientContextData::class.java)
|
||||||
|
} catch (err: Throwable) {
|
||||||
|
LOGGER.warn("Exception deserializing player context for ${alias()}, considering fresh context", err)
|
||||||
|
null
|
||||||
|
}
|
||||||
|
|
||||||
|
shipUpgrades = shipUpgrades.addCapability("planetTravel")
|
||||||
|
shipUpgrades = shipUpgrades.addCapability("teleport")
|
||||||
|
shipUpgrades = shipUpgrades.copy(maxFuel = 10000, shipLevel = 3)
|
||||||
|
|
||||||
|
scope.launch { warpEventLoop() }
|
||||||
|
|
||||||
|
if (context == null) {
|
||||||
|
shipWorld.eventLoop.execute { shipWorld.sky.startFlying(true, true) }
|
||||||
|
enqueueWarp(WarpAlias.OwnShip)
|
||||||
|
val startingLocation = findStartingSystem() ?: return
|
||||||
|
scope.launch { shipFlightEventLoop(startingLocation.location, SystemWorldLocation.Celestial(startingLocation)) }
|
||||||
|
} else {
|
||||||
|
if (context.returnWarp != null) {
|
||||||
|
enqueueWarp(context.returnWarp, ifFailed = WarpAlias.OwnShip)
|
||||||
|
} else {
|
||||||
|
enqueueWarp(WarpAlias.OwnShip)
|
||||||
|
}
|
||||||
|
|
||||||
|
scope.launch { shipFlightEventLoop(context.shipCoordinate, context.systemLocation) }
|
||||||
|
}
|
||||||
|
|
||||||
|
saveClientContextTask = channel.eventLoop().scheduleWithFixedDelay(Runnable { saveClientContext() }, 0L, 10L, TimeUnit.SECONDS)
|
||||||
|
|
||||||
|
scope.launch { saveClientContext() }
|
||||||
|
}
|
||||||
|
|
||||||
override fun inGame() {
|
override fun inGame() {
|
||||||
super.inGame()
|
super.inGame()
|
||||||
announcedDisconnect = false
|
announcedDisconnect = false
|
||||||
@ -498,16 +578,7 @@ class ServerConnection(val server: StarboundServer, type: ConnectionType) : Conn
|
|||||||
shipWorld.sky.referenceClock = server.universeClock
|
shipWorld.sky.referenceClock = server.universeClock
|
||||||
// shipWorld.sky.startFlying(true, true)
|
// shipWorld.sky.startFlying(true, true)
|
||||||
shipWorld.eventLoop.start()
|
shipWorld.eventLoop.start()
|
||||||
enqueueWarp(WarpAlias.OwnShip)
|
scope.launch { loadDataAndDispatchEventLoops() }
|
||||||
shipUpgrades = shipUpgrades.addCapability("planetTravel")
|
|
||||||
shipUpgrades = shipUpgrades.addCapability("teleport")
|
|
||||||
shipUpgrades = shipUpgrades.copy(maxFuel = 10000, shipLevel = 3)
|
|
||||||
scope.launch { shipFlightEventLoop() }
|
|
||||||
scope.launch { warpEventLoop() }
|
|
||||||
|
|
||||||
if (server.channels.connections.size > 1) {
|
|
||||||
enqueueWarp(WarpAction.Player(server.channels.connections.first().uuid!!))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}.exceptionally {
|
}.exceptionally {
|
||||||
LOGGER.error("Error while initializing shipworld for $this", it)
|
LOGGER.error("Error while initializing shipworld for $this", it)
|
||||||
|
@ -1,6 +1,12 @@
|
|||||||
package ru.dbotthepony.kstarbound.server
|
package ru.dbotthepony.kstarbound.server
|
||||||
|
|
||||||
|
import com.github.benmanes.caffeine.cache.CacheLoader
|
||||||
|
import com.github.benmanes.caffeine.cache.Caffeine
|
||||||
|
import com.google.gson.JsonElement
|
||||||
|
import com.google.gson.JsonObject
|
||||||
import com.google.gson.JsonPrimitive
|
import com.google.gson.JsonPrimitive
|
||||||
|
import it.unimi.dsi.fastutil.io.FastByteArrayInputStream
|
||||||
|
import it.unimi.dsi.fastutil.io.FastByteArrayOutputStream
|
||||||
import it.unimi.dsi.fastutil.objects.ObjectArraySet
|
import it.unimi.dsi.fastutil.objects.ObjectArraySet
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.SupervisorJob
|
import kotlinx.coroutines.SupervisorJob
|
||||||
@ -10,6 +16,7 @@ import kotlinx.coroutines.future.asCompletableFuture
|
|||||||
import kotlinx.coroutines.future.await
|
import kotlinx.coroutines.future.await
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import org.apache.logging.log4j.LogManager
|
import org.apache.logging.log4j.LogManager
|
||||||
|
import ru.dbotthepony.kommons.util.KOptional
|
||||||
import ru.dbotthepony.kstarbound.math.vector.Vector3i
|
import ru.dbotthepony.kstarbound.math.vector.Vector3i
|
||||||
import ru.dbotthepony.kstarbound.Globals
|
import ru.dbotthepony.kstarbound.Globals
|
||||||
import ru.dbotthepony.kstarbound.Starbound
|
import ru.dbotthepony.kstarbound.Starbound
|
||||||
@ -18,6 +25,10 @@ import ru.dbotthepony.kstarbound.defs.world.AsteroidsWorldParameters
|
|||||||
import ru.dbotthepony.kstarbound.defs.world.FloatingDungeonWorldParameters
|
import ru.dbotthepony.kstarbound.defs.world.FloatingDungeonWorldParameters
|
||||||
import ru.dbotthepony.kstarbound.defs.world.TerrestrialWorldParameters
|
import ru.dbotthepony.kstarbound.defs.world.TerrestrialWorldParameters
|
||||||
import ru.dbotthepony.kstarbound.defs.world.WorldTemplate
|
import ru.dbotthepony.kstarbound.defs.world.WorldTemplate
|
||||||
|
import ru.dbotthepony.kstarbound.json.readJsonElement
|
||||||
|
import ru.dbotthepony.kstarbound.json.readJsonObject
|
||||||
|
import ru.dbotthepony.kstarbound.json.writeJsonElement
|
||||||
|
import ru.dbotthepony.kstarbound.json.writeJsonObject
|
||||||
import ru.dbotthepony.kstarbound.network.packets.clientbound.UniverseTimeUpdatePacket
|
import ru.dbotthepony.kstarbound.network.packets.clientbound.UniverseTimeUpdatePacket
|
||||||
import ru.dbotthepony.kstarbound.server.world.LegacyWorldStorage
|
import ru.dbotthepony.kstarbound.server.world.LegacyWorldStorage
|
||||||
import ru.dbotthepony.kstarbound.server.world.ServerUniverse
|
import ru.dbotthepony.kstarbound.server.world.ServerUniverse
|
||||||
@ -26,8 +37,13 @@ import ru.dbotthepony.kstarbound.server.world.ServerSystemWorld
|
|||||||
import ru.dbotthepony.kstarbound.server.world.WorldStorage
|
import ru.dbotthepony.kstarbound.server.world.WorldStorage
|
||||||
import ru.dbotthepony.kstarbound.util.BlockableEventLoop
|
import ru.dbotthepony.kstarbound.util.BlockableEventLoop
|
||||||
import ru.dbotthepony.kstarbound.util.JVMClock
|
import ru.dbotthepony.kstarbound.util.JVMClock
|
||||||
|
import ru.dbotthepony.kstarbound.util.asStringOrNull
|
||||||
import ru.dbotthepony.kstarbound.util.random.random
|
import ru.dbotthepony.kstarbound.util.random.random
|
||||||
|
import ru.dbotthepony.kstarbound.util.toStarboundString
|
||||||
|
import ru.dbotthepony.kstarbound.util.uuidFromStarboundString
|
||||||
import ru.dbotthepony.kstarbound.world.UniversePos
|
import ru.dbotthepony.kstarbound.world.UniversePos
|
||||||
|
import java.io.DataInputStream
|
||||||
|
import java.io.DataOutputStream
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.sql.DriverManager
|
import java.sql.DriverManager
|
||||||
import java.util.UUID
|
import java.util.UUID
|
||||||
@ -56,24 +72,90 @@ sealed class StarboundServer(val root: File) : BlockableEventLoop("Server thread
|
|||||||
val chat = ChatHandler(this)
|
val chat = ChatHandler(this)
|
||||||
val globalScope = CoroutineScope(Starbound.COROUTINE_EXECUTOR + SupervisorJob())
|
val globalScope = CoroutineScope(Starbound.COROUTINE_EXECUTOR + SupervisorJob())
|
||||||
|
|
||||||
|
private val database = DriverManager.getConnection("jdbc:sqlite:${File(universeFolder, "universe.db").absolutePath.replace('\\', '/')}")
|
||||||
|
|
||||||
|
init {
|
||||||
|
database.autoCommit = false
|
||||||
|
|
||||||
|
database.createStatement().use {
|
||||||
|
it.execute("CREATE TABLE IF NOT EXISTS `metadata` (`key` VARCHAR NOT NULL PRIMARY KEY, `value` BLOB NOT NULL)")
|
||||||
|
it.execute("CREATE TABLE IF NOT EXISTS `universe_flags` (`flag` VARCHAR NOT NULL PRIMARY KEY)")
|
||||||
|
it.execute("CREATE TABLE IF NOT EXISTS `client_context` (`uuid` VARCHAR NOT NULL PRIMARY KEY, `data` BLOB NOT NULL)")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private val lookupMetadata = database.prepareStatement("SELECT `value` FROM `metadata` WHERE `key` = ?")
|
||||||
|
private val writeMetadata = database.prepareStatement("REPLACE INTO `metadata` (`key`, `value`) VALUES (?, ?)")
|
||||||
|
private val lookupClientContext = database.prepareStatement("SELECT `data` FROM `client_context` WHERE `uuid` = ?")
|
||||||
|
private val writeClientContext = database.prepareStatement("REPLACE INTO `client_context` (`uuid`, `data`) VALUES (?, ?)")
|
||||||
|
|
||||||
|
private fun getMetadata(key: String): KOptional<JsonElement> {
|
||||||
|
lookupMetadata.setString(1, key)
|
||||||
|
|
||||||
|
return lookupMetadata.executeQuery().use {
|
||||||
|
if (it.next()) {
|
||||||
|
KOptional(it.getBytes(1).readJsonElement())
|
||||||
|
} else {
|
||||||
|
KOptional()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun setMetadata(key: String, value: JsonElement) {
|
||||||
|
writeMetadata.setString(1, key)
|
||||||
|
writeMetadata.setBytes(2, value.writeJsonElement())
|
||||||
|
writeMetadata.execute()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun loadClientContext(uuid: UUID): CompletableFuture<JsonObject?> {
|
||||||
|
return supplyAsync {
|
||||||
|
lookupClientContext.setString(1, uuid.toStarboundString())
|
||||||
|
|
||||||
|
lookupClientContext.executeQuery().use {
|
||||||
|
if (it.next()) {
|
||||||
|
it.getBytes(1).readJsonObject()
|
||||||
|
} else {
|
||||||
|
null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun writeClientContext(uuid: UUID, context: JsonObject): CompletableFuture<*> {
|
||||||
|
return supplyAsync {
|
||||||
|
writeClientContext.setString(1, uuid.toStarboundString())
|
||||||
|
writeClientContext.setBytes(2, context.writeJsonObject())
|
||||||
|
writeClientContext.execute()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
val settings = ServerSettings()
|
val settings = ServerSettings()
|
||||||
val channels = ServerChannels(this)
|
val channels = ServerChannels(this)
|
||||||
val lock = ReentrantLock()
|
val lock = ReentrantLock()
|
||||||
var isClosed = false
|
var isClosed = false
|
||||||
private set
|
private set
|
||||||
|
|
||||||
var serverUUID: UUID = UUID.randomUUID()
|
val serverUUID: UUID = uuidFromStarboundString(getMetadata("server_uuid").orElse { JsonPrimitive(UUID.randomUUID().toStarboundString()) }.asString)
|
||||||
protected set
|
|
||||||
|
|
||||||
val universeClock = JVMClock()
|
val universeClock = JVMClock()
|
||||||
|
|
||||||
private val systemWorlds = HashMap<Vector3i, CompletableFuture<ServerSystemWorld>>()
|
init {
|
||||||
|
universeClock.set(getMetadata("universe_clock").orElse { JsonPrimitive(0.0) }.asDouble)
|
||||||
private suspend fun loadSystemWorld0(location: Vector3i): ServerSystemWorld {
|
setMetadata("server_uuid", JsonPrimitive(serverUUID.toStarboundString()))
|
||||||
return ServerSystemWorld.create(this, location)
|
database.commit()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun loadSystemWorld(location: Vector3i): CompletableFuture<ServerSystemWorld> {
|
private val systemWorlds = HashMap<Vector3i, CompletableFuture<ServerSystemWorld?>>()
|
||||||
|
|
||||||
|
private suspend fun loadSystemWorld0(location: Vector3i): ServerSystemWorld? {
|
||||||
|
try {
|
||||||
|
return ServerSystemWorld.create(this, location)
|
||||||
|
} catch (err: Throwable) {
|
||||||
|
LOGGER.error("Exception loading system world at $location", err)
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun loadSystemWorld(location: Vector3i): CompletableFuture<ServerSystemWorld?> {
|
||||||
return supplyAsync {
|
return supplyAsync {
|
||||||
systemWorlds.computeIfAbsent(location) {
|
systemWorlds.computeIfAbsent(location) {
|
||||||
globalScope.async { loadSystemWorld0(location) }.asCompletableFuture()
|
globalScope.async { loadSystemWorld0(location) }.asCompletableFuture()
|
||||||
@ -82,7 +164,7 @@ sealed class StarboundServer(val root: File) : BlockableEventLoop("Server thread
|
|||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun loadCelestialWorld(location: WorldID.Celestial): ServerWorld {
|
private suspend fun loadCelestialWorld(location: WorldID.Celestial): ServerWorld {
|
||||||
val file = File(universeFolder, location.pos.toString().replace(':', '_') + ".kworld")
|
val file = File(universeFolder, location.pos.toString().replace(':', '_') + ".db")
|
||||||
val firstTime = !file.exists()
|
val firstTime = !file.exists()
|
||||||
val storage = LegacyWorldStorage.SQL(file)
|
val storage = LegacyWorldStorage.SQL(file)
|
||||||
|
|
||||||
@ -197,21 +279,22 @@ sealed class StarboundServer(val root: File) : BlockableEventLoop("Server thread
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun loadSystemWorld(location: UniversePos): CompletableFuture<ServerSystemWorld> {
|
|
||||||
return loadSystemWorld(location.location)
|
|
||||||
}
|
|
||||||
|
|
||||||
init {
|
init {
|
||||||
scheduleAtFixedRate(Runnable {
|
scheduleAtFixedRate(Runnable {
|
||||||
channels.broadcast(UniverseTimeUpdatePacket(universeClock.time), false)
|
channels.broadcast(UniverseTimeUpdatePacket(universeClock.time), false)
|
||||||
|
setMetadata("universe_clock", JsonPrimitive(universeClock.time))
|
||||||
}, Globals.universeServer.clockUpdatePacketInterval, Globals.universeServer.clockUpdatePacketInterval, TimeUnit.MILLISECONDS)
|
}, Globals.universeServer.clockUpdatePacketInterval, Globals.universeServer.clockUpdatePacketInterval, TimeUnit.MILLISECONDS)
|
||||||
|
|
||||||
|
scheduleWithFixedDelay(Runnable {
|
||||||
|
database.commit()
|
||||||
|
}, Globals.universeServer.universeStorageInterval, Globals.universeServer.universeStorageInterval, TimeUnit.MILLISECONDS)
|
||||||
|
|
||||||
scheduleAtFixedRate(Runnable {
|
scheduleAtFixedRate(Runnable {
|
||||||
tickNormal(Starbound.TIMESTEP)
|
tickNormal(Starbound.TIMESTEP)
|
||||||
}, Starbound.TIMESTEP_NANOS, Starbound.TIMESTEP_NANOS, TimeUnit.NANOSECONDS)
|
}, Starbound.TIMESTEP_NANOS, Starbound.TIMESTEP_NANOS, TimeUnit.NANOSECONDS)
|
||||||
|
|
||||||
scheduleAtFixedRate(Runnable {
|
scheduleAtFixedRate(Runnable {
|
||||||
tickSystemWorlds()
|
tickSystemWorlds(Starbound.SYSTEM_WORLD_TIMESTEP)
|
||||||
}, Starbound.SYSTEM_WORLD_TIMESTEP_NANOS, Starbound.SYSTEM_WORLD_TIMESTEP_NANOS, TimeUnit.NANOSECONDS)
|
}, Starbound.SYSTEM_WORLD_TIMESTEP_NANOS, Starbound.SYSTEM_WORLD_TIMESTEP_NANOS, TimeUnit.NANOSECONDS)
|
||||||
|
|
||||||
isDaemon = false
|
isDaemon = false
|
||||||
@ -250,7 +333,7 @@ sealed class StarboundServer(val root: File) : BlockableEventLoop("Server thread
|
|||||||
protected abstract fun close0()
|
protected abstract fun close0()
|
||||||
protected abstract fun tick0(delta: Double)
|
protected abstract fun tick0(delta: Double)
|
||||||
|
|
||||||
private fun tickSystemWorlds() {
|
private fun tickSystemWorlds(delta: Double) {
|
||||||
systemWorlds.values.removeIf {
|
systemWorlds.values.removeIf {
|
||||||
if (it.isCompletedExceptionally) {
|
if (it.isCompletedExceptionally) {
|
||||||
return@removeIf true
|
return@removeIf true
|
||||||
@ -260,15 +343,19 @@ sealed class StarboundServer(val root: File) : BlockableEventLoop("Server thread
|
|||||||
return@removeIf false
|
return@removeIf false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (it.get() == null) {
|
||||||
|
return@removeIf true
|
||||||
|
}
|
||||||
|
|
||||||
scope.launch {
|
scope.launch {
|
||||||
try {
|
try {
|
||||||
it.get().tick(Starbound.SYSTEM_WORLD_TIMESTEP)
|
it.get()!!.tick(delta)
|
||||||
} catch (err: Throwable) {
|
} catch (err: Throwable) {
|
||||||
LOGGER.fatal("Exception in system world $it event loop", err)
|
LOGGER.fatal("Exception in system world $it event loop", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (it.get().shouldClose()) {
|
if (it.get()!!.shouldClose()) {
|
||||||
LOGGER.info("Stopping idling ${it.get()}")
|
LOGGER.info("Stopping idling ${it.get()}")
|
||||||
return@removeIf true
|
return@removeIf true
|
||||||
}
|
}
|
||||||
@ -322,6 +409,8 @@ sealed class StarboundServer(val root: File) : BlockableEventLoop("Server thread
|
|||||||
it.cancel(true)
|
it.cancel(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
database.commit()
|
||||||
|
database.close()
|
||||||
universe.close()
|
universe.close()
|
||||||
close0()
|
close0()
|
||||||
}
|
}
|
||||||
|
@ -264,6 +264,24 @@ class ServerSystemWorld : SystemWorld {
|
|||||||
next = tasks.poll()
|
next = tasks.poll()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// safeguard for cases when client wasn't removed properly
|
||||||
|
ships.values.removeIf {
|
||||||
|
if (it.shouldRemove()) {
|
||||||
|
val packet = SystemObjectDestroyPacket(it.uuid)
|
||||||
|
|
||||||
|
ships.values.forEach { ship ->
|
||||||
|
if (ship !== it) {
|
||||||
|
ship.forget(it.uuid)
|
||||||
|
ship.client.send(packet)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
true
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
entities.values.forEach { it.tick(delta) }
|
entities.values.forEach { it.tick(delta) }
|
||||||
ships.values.forEach { it.tick(delta) }
|
ships.values.forEach { it.tick(delta) }
|
||||||
|
|
||||||
@ -384,6 +402,10 @@ class ServerSystemWorld : SystemWorld {
|
|||||||
client.send(SystemWorldUpdatePacket(objects, ships))
|
client.send(SystemWorldUpdatePacket(objects, ships))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun shouldRemove(): Boolean {
|
||||||
|
return !client.isConnected
|
||||||
|
}
|
||||||
|
|
||||||
fun forget(id: UUID) {
|
fun forget(id: UUID) {
|
||||||
netVersions.removeLong(id)
|
netVersions.removeLong(id)
|
||||||
}
|
}
|
||||||
@ -499,12 +521,19 @@ class ServerSystemWorld : SystemWorld {
|
|||||||
companion object {
|
companion object {
|
||||||
private val LOGGER = LogManager.getLogger()
|
private val LOGGER = LogManager.getLogger()
|
||||||
|
|
||||||
suspend fun create(server: StarboundServer, location: Vector3i): ServerSystemWorld {
|
suspend fun create(server: StarboundServer, location: Vector3i): ServerSystemWorld? {
|
||||||
LOGGER.info("Creating new System World at $location")
|
val anything = server.universe.parameters(UniversePos(location))
|
||||||
val world = ServerSystemWorld(server, location)
|
|
||||||
world.spawnInitialObjects()
|
if (anything == null) {
|
||||||
world.spawnObjects()
|
LOGGER.warn("Tried to create system world at $location, but nothing is there")
|
||||||
return world
|
return null
|
||||||
|
} else {
|
||||||
|
LOGGER.info("Creating new System World at $location")
|
||||||
|
val world = ServerSystemWorld(server, location)
|
||||||
|
world.spawnInitialObjects()
|
||||||
|
world.spawnObjects()
|
||||||
|
return world
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun load(server: StarboundServer, data: JsonElement): ServerSystemWorld {
|
suspend fun load(server: StarboundServer, data: JsonElement): ServerSystemWorld {
|
||||||
|
@ -69,7 +69,7 @@ class ServerWorld private constructor(
|
|||||||
val clients = CopyOnWriteArrayList<ServerWorldTracker>()
|
val clients = CopyOnWriteArrayList<ServerWorldTracker>()
|
||||||
val shouldStopOnIdle = worldID !is WorldID.ShipWorld
|
val shouldStopOnIdle = worldID !is WorldID.ShipWorld
|
||||||
|
|
||||||
private fun doAcceptClient(client: ServerConnection, action: WarpAction?) {
|
private suspend fun doAcceptClient(client: ServerConnection, action: WarpAction?) {
|
||||||
try {
|
try {
|
||||||
isBusy++
|
isBusy++
|
||||||
|
|
||||||
@ -108,7 +108,7 @@ class ServerWorld private constructor(
|
|||||||
check(!eventLoop.isShutdown) { "$this is invalid" }
|
check(!eventLoop.isShutdown) { "$this is invalid" }
|
||||||
|
|
||||||
try {
|
try {
|
||||||
val future = eventLoop.supplyAsync { doAcceptClient(player, action) }
|
val future = eventLoop.scope.async { doAcceptClient(player, action) }.asCompletableFuture()
|
||||||
|
|
||||||
future.exceptionally {
|
future.exceptionally {
|
||||||
LOGGER.error("Error while accepting new player into world", it)
|
LOGGER.error("Error while accepting new player into world", it)
|
||||||
|
@ -380,8 +380,10 @@ class ServerWorldTracker(val world: ServerWorld, val client: ServerConnection, p
|
|||||||
|
|
||||||
val playerEntity = client.playerEntity
|
val playerEntity = client.playerEntity
|
||||||
|
|
||||||
if (playerEntity != null && world.worldID is WorldID.Celestial && setReturnWarp) {
|
if (playerEntity != null && (world.worldID is WorldID.Celestial || world.worldID is WorldID.ShipWorld && world.worldID.uuid == client.uuid) && setReturnWarp) {
|
||||||
client.returnWarp = WarpAction.World(world.worldID, SpawnTarget.Position(playerEntity.position))
|
client.returnWarp = WarpAction.World(world.worldID, SpawnTarget.Position(playerEntity.position))
|
||||||
|
} else if (setReturnWarp) {
|
||||||
|
client.returnWarp = null
|
||||||
}
|
}
|
||||||
|
|
||||||
if (nullifyVariables) {
|
if (nullifyVariables) {
|
||||||
|
@ -111,6 +111,11 @@ class JVMClock : IClock {
|
|||||||
baseline = nanos
|
baseline = nanos
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun set(seconds: Double) {
|
||||||
|
origin = System.nanoTime()
|
||||||
|
baseline = (seconds * 1_000_000_000L).toLong()
|
||||||
|
}
|
||||||
|
|
||||||
fun pause() {
|
fun pause() {
|
||||||
if (!isPaused) {
|
if (!isPaused) {
|
||||||
baseline += System.nanoTime() - origin
|
baseline += System.nanoTime() - origin
|
||||||
|
@ -49,7 +49,7 @@ fun uuidFromStarboundString(value: String): UUID {
|
|||||||
val a = value.substring(0, 16)
|
val a = value.substring(0, 16)
|
||||||
val b = value.substring(16)
|
val b = value.substring(16)
|
||||||
|
|
||||||
return UUID(a.toLong(16), b.toLong(16))
|
return UUID(java.lang.Long.parseUnsignedLong(a, 16), java.lang.Long.parseUnsignedLong(b, 16))
|
||||||
}
|
}
|
||||||
|
|
||||||
fun paddedNumber(number: Int, digits: Int): String {
|
fun paddedNumber(number: Int, digits: Int): String {
|
||||||
|
@ -1,6 +1,10 @@
|
|||||||
package ru.dbotthepony.kstarbound.world
|
package ru.dbotthepony.kstarbound.world
|
||||||
|
|
||||||
import com.google.common.collect.ImmutableList
|
import com.google.common.collect.ImmutableList
|
||||||
|
import com.google.gson.JsonObject
|
||||||
|
import com.google.gson.TypeAdapter
|
||||||
|
import com.google.gson.annotations.JsonAdapter
|
||||||
|
import com.google.gson.reflect.TypeToken
|
||||||
import ru.dbotthepony.kommons.io.readUUID
|
import ru.dbotthepony.kommons.io.readUUID
|
||||||
import ru.dbotthepony.kommons.io.writeUUID
|
import ru.dbotthepony.kommons.io.writeUUID
|
||||||
import ru.dbotthepony.kommons.util.KOptional
|
import ru.dbotthepony.kommons.util.KOptional
|
||||||
@ -14,6 +18,9 @@ import ru.dbotthepony.kstarbound.defs.world.AsteroidsWorldParameters
|
|||||||
import ru.dbotthepony.kstarbound.defs.world.SkyParameters
|
import ru.dbotthepony.kstarbound.defs.world.SkyParameters
|
||||||
import ru.dbotthepony.kstarbound.io.readVector2d
|
import ru.dbotthepony.kstarbound.io.readVector2d
|
||||||
import ru.dbotthepony.kstarbound.io.writeStruct2d
|
import ru.dbotthepony.kstarbound.io.writeStruct2d
|
||||||
|
import ru.dbotthepony.kstarbound.json.builder.DispatchingAdapter
|
||||||
|
import ru.dbotthepony.kstarbound.json.builder.JsonFactory
|
||||||
|
import ru.dbotthepony.kstarbound.json.builder.JsonSingleton
|
||||||
import ru.dbotthepony.kstarbound.network.syncher.legacyCodec
|
import ru.dbotthepony.kstarbound.network.syncher.legacyCodec
|
||||||
import ru.dbotthepony.kstarbound.network.syncher.nativeCodec
|
import ru.dbotthepony.kstarbound.network.syncher.nativeCodec
|
||||||
import ru.dbotthepony.kstarbound.util.random.random
|
import ru.dbotthepony.kstarbound.util.random.random
|
||||||
@ -30,6 +37,16 @@ sealed class SystemWorldLocation {
|
|||||||
abstract suspend fun orbitalAction(system: SystemWorld): KOptional<Pair<WarpAction, WarpMode>>
|
abstract suspend fun orbitalAction(system: SystemWorld): KOptional<Pair<WarpAction, WarpMode>>
|
||||||
abstract suspend fun skyParameters(system: SystemWorld): SkyParameters
|
abstract suspend fun skyParameters(system: SystemWorld): SkyParameters
|
||||||
|
|
||||||
|
enum class Type(val token: TypeToken<out SystemWorldLocation>) {
|
||||||
|
TRANSIT(TypeToken.get(Transit::class.java)),
|
||||||
|
CELESTIAL(TypeToken.get(Celestial::class.java)),
|
||||||
|
ORBIT(TypeToken.get(Orbit::class.java)),
|
||||||
|
ENTITY(TypeToken.get(Entity::class.java)),
|
||||||
|
POSITION(TypeToken.get(Position::class.java));
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract val type: Type
|
||||||
|
|
||||||
protected suspend fun appendParameters(parameters: SkyParameters, system: SystemWorld, orbit: UniversePos): SkyParameters {
|
protected suspend fun appendParameters(parameters: SkyParameters, system: SystemWorld, orbit: UniversePos): SkyParameters {
|
||||||
val planets = ArrayList<UniversePos>()
|
val planets = ArrayList<UniversePos>()
|
||||||
|
|
||||||
@ -58,6 +75,7 @@ sealed class SystemWorldLocation {
|
|||||||
return parameters
|
return parameters
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@JsonSingleton
|
||||||
object Transit : SystemWorldLocation() {
|
object Transit : SystemWorldLocation() {
|
||||||
override fun write(stream: DataOutputStream, isLegacy: Boolean) {
|
override fun write(stream: DataOutputStream, isLegacy: Boolean) {
|
||||||
stream.writeByte(0)
|
stream.writeByte(0)
|
||||||
@ -74,9 +92,13 @@ sealed class SystemWorldLocation {
|
|||||||
override suspend fun skyParameters(system: SystemWorld): SkyParameters {
|
override suspend fun skyParameters(system: SystemWorld): SkyParameters {
|
||||||
return Globals.systemWorld.emptySkyParameters
|
return Globals.systemWorld.emptySkyParameters
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override val type: Type
|
||||||
|
get() = Type.TRANSIT
|
||||||
}
|
}
|
||||||
|
|
||||||
// orbiting around specific planet
|
// orbiting around specific planet
|
||||||
|
@JsonFactory
|
||||||
data class Celestial(val position: UniversePos) : SystemWorldLocation() {
|
data class Celestial(val position: UniversePos) : SystemWorldLocation() {
|
||||||
override fun write(stream: DataOutputStream, isLegacy: Boolean) {
|
override fun write(stream: DataOutputStream, isLegacy: Boolean) {
|
||||||
stream.writeByte(1)
|
stream.writeByte(1)
|
||||||
@ -94,9 +116,13 @@ sealed class SystemWorldLocation {
|
|||||||
override suspend fun skyParameters(system: SystemWorld): SkyParameters {
|
override suspend fun skyParameters(system: SystemWorld): SkyParameters {
|
||||||
return SkyParameters.create(position, system.universe)
|
return SkyParameters.create(position, system.universe)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override val type: Type
|
||||||
|
get() = Type.CELESTIAL
|
||||||
}
|
}
|
||||||
|
|
||||||
// orbiting around celestial body
|
// orbiting around celestial body
|
||||||
|
@JsonFactory
|
||||||
data class Orbit(val position: SystemWorld.Orbit) : SystemWorldLocation() {
|
data class Orbit(val position: SystemWorld.Orbit) : SystemWorldLocation() {
|
||||||
override fun write(stream: DataOutputStream, isLegacy: Boolean) {
|
override fun write(stream: DataOutputStream, isLegacy: Boolean) {
|
||||||
stream.writeByte(2)
|
stream.writeByte(2)
|
||||||
@ -117,8 +143,12 @@ sealed class SystemWorldLocation {
|
|||||||
// (but that is still technically possible to outer-orbit a satellite)
|
// (but that is still technically possible to outer-orbit a satellite)
|
||||||
return appendParameters(Globals.systemWorld.emptySkyParameters.copy(), system, position.target)
|
return appendParameters(Globals.systemWorld.emptySkyParameters.copy(), system, position.target)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override val type: Type
|
||||||
|
get() = Type.ORBIT
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@JsonFactory
|
||||||
data class Entity(val uuid: UUID) : SystemWorldLocation() {
|
data class Entity(val uuid: UUID) : SystemWorldLocation() {
|
||||||
override fun write(stream: DataOutputStream, isLegacy: Boolean) {
|
override fun write(stream: DataOutputStream, isLegacy: Boolean) {
|
||||||
stream.writeByte(3)
|
stream.writeByte(3)
|
||||||
@ -150,8 +180,12 @@ sealed class SystemWorldLocation {
|
|||||||
|
|
||||||
return sky
|
return sky
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override val type: Type
|
||||||
|
get() = Type.ENTITY
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@JsonFactory
|
||||||
data class Position(val position: Vector2d) : SystemWorldLocation() {
|
data class Position(val position: Vector2d) : SystemWorldLocation() {
|
||||||
override fun write(stream: DataOutputStream, isLegacy: Boolean) {
|
override fun write(stream: DataOutputStream, isLegacy: Boolean) {
|
||||||
stream.writeByte(4)
|
stream.writeByte(4)
|
||||||
@ -197,9 +231,13 @@ sealed class SystemWorldLocation {
|
|||||||
|
|
||||||
return Globals.systemWorld.emptySkyParameters
|
return Globals.systemWorld.emptySkyParameters
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override val type: Type
|
||||||
|
get() = Type.POSITION
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
val ADAPTER = DispatchingAdapter("type", { type }, { token }, Type.entries)
|
||||||
val CODEC = nativeCodec(::read, SystemWorldLocation::write)
|
val CODEC = nativeCodec(::read, SystemWorldLocation::write)
|
||||||
val LEGACY_CODEC = legacyCodec(::read, SystemWorldLocation::write)
|
val LEGACY_CODEC = legacyCodec(::read, SystemWorldLocation::write)
|
||||||
|
|
||||||
|
@ -1,21 +1,16 @@
|
|||||||
package ru.dbotthepony.kstarbound.world
|
package ru.dbotthepony.kstarbound.world
|
||||||
|
|
||||||
import com.google.gson.Gson
|
import com.google.gson.Gson
|
||||||
import com.google.gson.JsonObject
|
|
||||||
import com.google.gson.JsonSyntaxException
|
import com.google.gson.JsonSyntaxException
|
||||||
import com.google.gson.TypeAdapter
|
import com.google.gson.TypeAdapter
|
||||||
import com.google.gson.TypeAdapterFactory
|
|
||||||
import com.google.gson.annotations.JsonAdapter
|
import com.google.gson.annotations.JsonAdapter
|
||||||
import com.google.gson.reflect.TypeToken
|
|
||||||
import com.google.gson.stream.JsonReader
|
import com.google.gson.stream.JsonReader
|
||||||
import com.google.gson.stream.JsonToken
|
import com.google.gson.stream.JsonToken
|
||||||
import com.google.gson.stream.JsonWriter
|
import com.google.gson.stream.JsonWriter
|
||||||
import ru.dbotthepony.kommons.gson.get
|
import ru.dbotthepony.kommons.gson.get
|
||||||
import ru.dbotthepony.kstarbound.math.vector.Vector3i
|
import ru.dbotthepony.kstarbound.math.vector.Vector3i
|
||||||
import ru.dbotthepony.kommons.gson.consumeNull
|
|
||||||
import ru.dbotthepony.kommons.io.readVarInt
|
import ru.dbotthepony.kommons.io.readVarInt
|
||||||
import ru.dbotthepony.kstarbound.io.readVector3i
|
import ru.dbotthepony.kstarbound.io.readVector3i
|
||||||
import ru.dbotthepony.kommons.io.writeSignedVarInt
|
|
||||||
import ru.dbotthepony.kommons.io.writeStruct3i
|
import ru.dbotthepony.kommons.io.writeStruct3i
|
||||||
import ru.dbotthepony.kommons.io.writeVarInt
|
import ru.dbotthepony.kommons.io.writeVarInt
|
||||||
import ru.dbotthepony.kstarbound.Starbound
|
import ru.dbotthepony.kstarbound.Starbound
|
||||||
@ -144,8 +139,8 @@ data class UniversePos(val location: Vector3i = Vector3i.ZERO, val planetOrbit:
|
|||||||
val values = Starbound.ELEMENTS_ADAPTER.objects.read(`in`)!!
|
val values = Starbound.ELEMENTS_ADAPTER.objects.read(`in`)!!
|
||||||
val location = values.get("location", vectors)
|
val location = values.get("location", vectors)
|
||||||
val planet = values.get("planet", 0)
|
val planet = values.get("planet", 0)
|
||||||
val orbit = values.get("orbit", 0)
|
val satellite = values.get("satellite", 0)
|
||||||
return UniversePos(location, planet, orbit)
|
return UniversePos(location, planet, satellite)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (`in`.peek() == JsonToken.STRING) {
|
if (`in`.peek() == JsonToken.STRING) {
|
||||||
|
Loading…
Reference in New Issue
Block a user