Player warping test code

This commit is contained in:
DBotThePony 2024-03-29 17:03:39 +07:00
parent f2c2db2e9d
commit 21a13134a4
Signed by: DBot
GPG Key ID: DCC23B5715498507
20 changed files with 580 additions and 89 deletions

View File

@ -103,7 +103,7 @@ fun main() {
Starbound.initializeGame() Starbound.initializeGame()
Starbound.mailboxInitialized.submit { Starbound.mailboxInitialized.submit {
val server = IntegratedStarboundServer(File("./")) val server = IntegratedStarboundServer(client, File("./"))
val world = ServerWorld.load(server, LegacyWorldStorage.file(db)).get() val world = ServerWorld.load(server, LegacyWorldStorage.file(db)).get()
world.thread.start() world.thread.start()

View File

@ -19,7 +19,6 @@ import ru.dbotthepony.kstarbound.network.packets.ProtocolRequestPacket
import ru.dbotthepony.kstarbound.network.packets.serverbound.ClientDisconnectRequestPacket import ru.dbotthepony.kstarbound.network.packets.serverbound.ClientDisconnectRequestPacket
import ru.dbotthepony.kstarbound.network.packets.serverbound.WorldClientStateUpdatePacket import ru.dbotthepony.kstarbound.network.packets.serverbound.WorldClientStateUpdatePacket
import java.net.SocketAddress import java.net.SocketAddress
import java.util.*
// clientside part of connection // clientside part of connection
class ClientConnection(val client: StarboundClient, type: ConnectionType) : Connection(ConnectionSide.CLIENT, type) { class ClientConnection(val client: StarboundClient, type: ConnectionType) : Connection(ConnectionSide.CLIENT, type) {
@ -71,7 +70,7 @@ class ClientConnection(val client: StarboundClient, type: ConnectionType) : Conn
channel.write(ClientContextUpdatePacket(entries, KOptional(), KOptional())) channel.write(ClientContextUpdatePacket(entries, KOptional(), KOptional()))
} }
val (data, new) = clientStateGroup.write(clientStateNetVersion) val (data, new) = client2serverGroup.write(clientStateNetVersion)
if (data.isNotEmpty()) if (data.isNotEmpty())
channel.write(WorldClientStateUpdatePacket(data)) channel.write(WorldClientStateUpdatePacket(data))

View File

@ -1,7 +1,23 @@
package ru.dbotthepony.kstarbound.defs package ru.dbotthepony.kstarbound.defs
import ru.dbotthepony.kommons.io.StreamCodec
import ru.dbotthepony.kommons.io.readUUID
import ru.dbotthepony.kommons.io.readVector2d
import ru.dbotthepony.kommons.io.readVector2f
import ru.dbotthepony.kommons.io.writeBinaryString
import ru.dbotthepony.kommons.io.writeStruct2d
import ru.dbotthepony.kommons.io.writeStruct2f
import ru.dbotthepony.kommons.io.writeUUID
import ru.dbotthepony.kommons.vector.Vector2d
import ru.dbotthepony.kstarbound.io.readInternedString
import ru.dbotthepony.kstarbound.json.builder.IStringSerializable
import ru.dbotthepony.kstarbound.network.syncher.legacyCodec
import ru.dbotthepony.kstarbound.network.syncher.nativeCodec
import ru.dbotthepony.kstarbound.server.ServerConnection
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
// original game has MVariant here // original game has MVariant here
// MVariant prepends InvalidValue to Variant<> template // MVariant prepends InvalidValue to Variant<> template
@ -11,35 +27,190 @@ import java.io.DataOutputStream
// -> Variant<InvalidType, WarpToWorld, WarpToPlayer, WarpAlias> WarpAction // -> Variant<InvalidType, WarpToWorld, WarpToPlayer, WarpAlias> WarpAction
// hence WarpToWorld has index 1, WarpToPlayer 2, WarpAlias 3 // hence WarpToWorld has index 1, WarpToPlayer 2, WarpAlias 3
sealed class AbstractWarpTarget { sealed class SpawnTarget {
abstract fun write(stream: DataOutputStream, isLegacy: Boolean) abstract fun write(stream: DataOutputStream, isLegacy: Boolean)
abstract fun resolve(world: ServerWorld): Vector2d?
object Whatever : SpawnTarget() {
override fun write(stream: DataOutputStream, isLegacy: Boolean) {
stream.writeByte(0)
}
override fun resolve(world: ServerWorld): Vector2d {
return world.playerSpawnPosition
}
override fun toString(): String {
return "SpawnTarget.SpawnTarget"
}
}
data class Entity(val id: String) : SpawnTarget() {
override fun write(stream: DataOutputStream, isLegacy: Boolean) {
stream.writeByte(1)
stream.writeBinaryString(id)
}
override fun resolve(world: ServerWorld): Vector2d? {
return world.entities.values.firstOrNull { it.uniqueID == id }?.position
}
override fun toString(): String {
return "SpawnTarget.Entity[$id]"
}
}
data class Position(val position: Vector2d) : SpawnTarget() {
override fun write(stream: DataOutputStream, isLegacy: Boolean) {
stream.writeByte(2)
if (isLegacy) {
stream.writeStruct2f(position.toFloatVector())
} else {
stream.writeStruct2d(position)
}
}
override fun toString(): String {
return "SpawnTarget.Position[$position]"
}
override fun resolve(world: ServerWorld): Vector2d {
return position
}
}
data class X(val position: Double) : SpawnTarget() {
override fun write(stream: DataOutputStream, isLegacy: Boolean) {
stream.writeByte(3)
if (isLegacy) {
stream.writeFloat(position.toFloat())
} else {
stream.writeDouble(position)
}
}
override fun toString(): String {
return "SpawnTarget.X[$position]"
}
override fun resolve(world: ServerWorld): Vector2d {
TODO("Not yet implemented")
}
}
companion object { companion object {
fun read(stream: DataInputStream, isLegacy: Boolean): AbstractWarpTarget { fun read(stream: DataInputStream, isLegacy: Boolean): SpawnTarget {
return when (stream.readUnsignedByte()) { return when (val type = stream.readUnsignedByte()) {
3 -> { 0 -> Whatever
when (stream.readInt()) { 1 -> Entity(stream.readInternedString())
0 -> WarpAlias.Return 2 -> Position(if (isLegacy) stream.readVector2f().toDoubleVector() else stream.readVector2d())
1 -> WarpAlias.OrbitedWorld 3 -> X(if (isLegacy) stream.readFloat().toDouble() else stream.readDouble())
2 -> WarpAlias.OwnShip else -> throw IllegalArgumentException("Unknown SpawnTarget type $type!")
else -> throw IllegalArgumentException()
}
}
else -> throw IllegalArgumentException()
} }
} }
} }
} }
sealed class WarpAlias(val index: Int) : AbstractWarpTarget() { sealed class WarpAction {
abstract fun write(stream: DataOutputStream, isLegacy: Boolean)
abstract fun resolve(connection: ServerConnection): WorldID
data class World(val worldID: WorldID, val target: SpawnTarget) : WarpAction() {
override fun write(stream: DataOutputStream, isLegacy: Boolean) {
stream.writeByte(1)
worldID.write(stream, isLegacy)
target.write(stream, isLegacy)
}
override fun resolve(connection: ServerConnection): WorldID {
TODO("Not yet implemented")
}
}
data class Player(val uuid: UUID) : WarpAction() {
override fun write(stream: DataOutputStream, isLegacy: Boolean) {
stream.writeByte(2)
stream.writeUUID(uuid)
}
override fun resolve(connection: ServerConnection): WorldID {
if (connection.uuid == uuid)
return connection.world?.worldID ?: WorldID.Limbo
return connection.server.clientByUUID(uuid)?.world?.worldID ?: WorldID.Limbo
}
}
companion object {
fun read(stream: DataInputStream, isLegacy: Boolean): WarpAction {
return when (val type = stream.readUnsignedByte()) {
1 -> World(WorldID.read(stream, isLegacy), SpawnTarget.read(stream, isLegacy))
2 -> Player(stream.readUUID())
3 -> {
when (val type2 = stream.readInt()) {
0 -> WarpAlias.Return
1 -> WarpAlias.OrbitedWorld
2 -> WarpAlias.OwnShip
else -> throw IllegalArgumentException("Unknown WarpAlias type $type2!")
}
}
else -> throw IllegalArgumentException("Unknown WarpAction type $type!")
}
}
val CODEC = nativeCodec(::read, WarpAction::write)
val LEGACY_CODEC = legacyCodec(::read, WarpAction::write)
}
}
sealed class WarpAlias(val index: Int) : WarpAction() {
override fun write(stream: DataOutputStream, isLegacy: Boolean) { override fun write(stream: DataOutputStream, isLegacy: Boolean) {
stream.write(3) stream.write(3)
// because it is defined as enum class WarpAlias, without specifying uint8_t as type // because it is defined as enum class WarpAlias, without specifying uint8_t as type
stream.writeInt(index) stream.writeInt(index)
} }
object Return : WarpAlias(0) object Return : WarpAlias(0) {
object OrbitedWorld : WarpAlias(1) override fun resolve(connection: ServerConnection): WorldID {
object OwnShip : WarpAlias(2) TODO("Not yet implemented")
}
override fun toString(): String {
return "WarpAlias.Return"
}
}
object OrbitedWorld : WarpAlias(1) {
override fun resolve(connection: ServerConnection): WorldID {
TODO("Not yet implemented")
}
override fun toString(): String {
return "WarpAlias.OrbitedWorld"
}
}
object OwnShip : WarpAlias(2) {
override fun resolve(connection: ServerConnection): WorldID {
return connection.shipWorld.worldID
}
override fun toString(): String {
return "WarpAlias.OwnShip"
}
}
}
enum class WarpMode(override val jsonName: String) : IStringSerializable {
NONE("None"),
BEAM_ONLY("BeamOnly"),
DEPLOY_ONLY("DeployOnly"),
BEAM_OR_DEPLOY("BeamOrDeploy");
companion object {
val CODEC = StreamCodec.Enum(WarpMode::class.java)
}
} }

View File

@ -0,0 +1,103 @@
package ru.dbotthepony.kstarbound.defs
import ru.dbotthepony.kommons.io.readUUID
import ru.dbotthepony.kommons.io.writeBinaryString
import ru.dbotthepony.kommons.io.writeUUID
import ru.dbotthepony.kstarbound.io.readInternedString
import ru.dbotthepony.kstarbound.network.syncher.legacyCodec
import ru.dbotthepony.kstarbound.network.syncher.nativeCodec
import ru.dbotthepony.kstarbound.world.UniversePos
import java.io.DataInputStream
import java.io.DataOutputStream
import java.util.UUID
sealed class WorldID {
abstract fun write(stream: DataOutputStream, isLegacy: Boolean)
val isLimbo: Boolean get() = this is Limbo
object Limbo : WorldID() {
override fun write(stream: DataOutputStream, isLegacy: Boolean) {
stream.writeByte(0)
}
override fun toString(): String {
return "WorldID.Limbo"
}
}
data class Celestial(val pos: UniversePos) : WorldID() {
override fun write(stream: DataOutputStream, isLegacy: Boolean) {
stream.writeByte(1)
pos.write(stream, isLegacy)
}
override fun toString(): String {
return "WorldID.Celestial[$pos]"
}
}
data class ShipWorld(val uuid: UUID) : WorldID() {
override fun write(stream: DataOutputStream, isLegacy: Boolean) {
stream.writeByte(2)
stream.writeUUID(uuid)
}
override fun toString(): String {
return "WorldID.ShipWorld[$uuid]"
}
}
data class Instance(val name: String, val uuid: UUID? = null, val threatLevel: Double? = null) : WorldID() {
override fun write(stream: DataOutputStream, isLegacy: Boolean) {
stream.writeByte(3)
stream.writeBinaryString(name)
stream.writeBoolean(uuid != null)
if (uuid != null) stream.writeUUID(uuid)
stream.writeBoolean(threatLevel != null)
if (threatLevel != null) {
if (isLegacy) {
stream.writeFloat(threatLevel.toFloat())
} else {
stream.writeDouble(threatLevel)
}
}
}
override fun toString(): String {
return "WorldID.Instance[$name, uuid=$uuid, threat level=$threatLevel]"
}
}
companion object {
val CODEC = nativeCodec(::read, WorldID::write)
val LEGACY_CODEC = legacyCodec(::read, WorldID::write)
fun read(stream: DataInputStream, isLegacy: Boolean): WorldID {
return when (val type = stream.readUnsignedByte()) {
0 -> Limbo
1 -> Celestial(UniversePos(stream, isLegacy))
2 -> ShipWorld(stream.readUUID())
3 -> {
val name = stream.readInternedString()
val uuid = if (stream.readBoolean()) stream.readUUID() else null
val level: Double?
if (stream.readBoolean()) {
if (isLegacy) {
level = stream.readFloat().toDouble()
} else {
level = stream.readDouble()
}
} else {
level = null
}
Instance(name, uuid, level)
}
else -> throw IllegalArgumentException("Unknown WorldID type $type!")
}
}
}
}

View File

@ -6,7 +6,10 @@ import ru.dbotthepony.kommons.io.readBinaryString
import ru.dbotthepony.kommons.io.readCollection import ru.dbotthepony.kommons.io.readCollection
import ru.dbotthepony.kommons.io.writeBinaryString import ru.dbotthepony.kommons.io.writeBinaryString
import ru.dbotthepony.kommons.io.writeCollection import ru.dbotthepony.kommons.io.writeCollection
import ru.dbotthepony.kstarbound.io.readInternedString
import ru.dbotthepony.kstarbound.json.builder.JsonFactory import ru.dbotthepony.kstarbound.json.builder.JsonFactory
import ru.dbotthepony.kstarbound.network.syncher.legacyCodec
import ru.dbotthepony.kstarbound.network.syncher.nativeCodec
import java.io.DataInputStream import java.io.DataInputStream
import java.io.DataOutputStream import java.io.DataOutputStream
@ -25,7 +28,7 @@ data class ShipUpgrades(
stream.readInt(), stream.readInt(),
if (isLegacy) stream.readFloat().toDouble() else stream.readDouble(), if (isLegacy) stream.readFloat().toDouble() else stream.readDouble(),
stream.readInt(), stream.readInt(),
ImmutableSet.copyOf(stream.readCollection { readBinaryString() }) ImmutableSet.copyOf(stream.readCollection { readInternedString() })
) )
fun apply(upgrades: ShipUpgrades): ShipUpgrades { fun apply(upgrades: ShipUpgrades): ShipUpgrades {
@ -52,4 +55,9 @@ data class ShipUpgrades(
stream.writeInt(shipSpeed) stream.writeInt(shipSpeed)
stream.writeCollection(capabilities) { writeBinaryString(it) } stream.writeCollection(capabilities) { writeBinaryString(it) }
} }
companion object {
val CODEC = nativeCodec(::ShipUpgrades, ShipUpgrades::write)
val LEGACY_CODEC = legacyCodec(::ShipUpgrades, ShipUpgrades::write)
}
} }

View File

@ -10,16 +10,28 @@ import it.unimi.dsi.fastutil.ints.IntAVLTreeSet
import org.apache.logging.log4j.LogManager 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.io.koptional
import ru.dbotthepony.kommons.util.AABBi import ru.dbotthepony.kommons.util.AABBi
import ru.dbotthepony.kommons.util.KOptional
import ru.dbotthepony.kommons.util.getValue
import ru.dbotthepony.kommons.util.setValue
import ru.dbotthepony.kommons.vector.Vector2d 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.defs.WarpAction
import ru.dbotthepony.kstarbound.defs.EntityDamageTeam
import ru.dbotthepony.kstarbound.defs.WarpMode
import ru.dbotthepony.kstarbound.defs.WorldID
import ru.dbotthepony.kstarbound.defs.actor.player.ShipUpgrades
import ru.dbotthepony.kstarbound.network.syncher.BasicNetworkedElement import ru.dbotthepony.kstarbound.network.syncher.BasicNetworkedElement
import ru.dbotthepony.kstarbound.network.syncher.NetworkedGroup import ru.dbotthepony.kstarbound.network.syncher.NetworkedGroup
import ru.dbotthepony.kstarbound.network.syncher.MasterElement import ru.dbotthepony.kstarbound.network.syncher.MasterElement
import ru.dbotthepony.kstarbound.network.syncher.networkedBoolean
import ru.dbotthepony.kstarbound.network.syncher.networkedData
import ru.dbotthepony.kstarbound.network.syncher.networkedSignedInt import ru.dbotthepony.kstarbound.network.syncher.networkedSignedInt
import ru.dbotthepony.kstarbound.player.Avatar import ru.dbotthepony.kstarbound.player.Avatar
import ru.dbotthepony.kstarbound.server.ServerChannels import ru.dbotthepony.kstarbound.server.ServerChannels
import ru.dbotthepony.kstarbound.world.UniversePos
import ru.dbotthepony.kstarbound.world.entities.player.PlayerEntity import ru.dbotthepony.kstarbound.world.entities.player.PlayerEntity
import java.io.Closeable import java.io.Closeable
import kotlin.math.roundToInt import kotlin.math.roundToInt
@ -150,24 +162,35 @@ abstract class Connection(val side: ConnectionSide, val type: ConnectionType) :
channel.close() channel.close()
} }
val windowXMin = networkedSignedInt() // global variables (per connection)
val windowYMin = networkedSignedInt() // clientside variables
val windowWidth = networkedSignedInt() val client2serverGroup = MasterElement(NetworkedGroup())
val windowHeight = networkedSignedInt() var windowXMin by client2serverGroup.upstream.add(networkedSignedInt())
val playerID = networkedSignedInt() var windowYMin by client2serverGroup.upstream.add(networkedSignedInt())
var windowWidth by client2serverGroup.upstream.add(networkedSignedInt())
var windowHeight by client2serverGroup.upstream.add(networkedSignedInt())
var playerID by client2serverGroup.upstream.add(networkedSignedInt())
// serverside variables
val server2clientGroup = MasterElement(NetworkedGroup())
var orbitalWarpAction by server2clientGroup.upstream.add(networkedData(KOptional(), warpActionCodec, legacyWarpActionCodec))
var worldID by server2clientGroup.upstream.add(networkedData(WorldID.Limbo, WorldID.CODEC, WorldID.LEGACY_CODEC))
var isAdmin by server2clientGroup.upstream.add(networkedBoolean())
var team by server2clientGroup.upstream.add(networkedData(EntityDamageTeam(), EntityDamageTeam.CODEC, EntityDamageTeam.LEGACY_CODEC))
var shipUpgrades by server2clientGroup.upstream.add(networkedData(ShipUpgrades(), ShipUpgrades.CODEC, ShipUpgrades.LEGACY_CODEC))
var shipCoordinate by server2clientGroup.upstream.add(networkedData(UniversePos(), UniversePos.CODEC, UniversePos.LEGACY_CODEC))
var playerEntity: PlayerEntity? = null var playerEntity: PlayerEntity? = null
// holy shit // holy shit
val clientSpectatingEntities = BasicNetworkedElement(IntAVLTreeSet(), StreamCodec.Collection(VarIntValueCodec) { IntAVLTreeSet() }) val clientSpectatingEntities = BasicNetworkedElement(IntAVLTreeSet(), StreamCodec.Collection(VarIntValueCodec) { IntAVLTreeSet() })
val clientStateGroup = MasterElement(NetworkedGroup(windowXMin, windowYMin, windowWidth, windowHeight, playerID, clientSpectatingEntities))
// in tiles // in tiles
fun trackingTileRegions(): List<AABBi> { fun trackingTileRegions(): List<AABBi> {
val result = ArrayList<AABBi>() val result = ArrayList<AABBi>()
var mins = Vector2i(windowXMin.get() - GlobalDefaults.client.windowMonitoringBorder, windowYMin.get() - GlobalDefaults.client.windowMonitoringBorder) var mins = Vector2i(windowXMin - GlobalDefaults.client.windowMonitoringBorder, windowYMin - GlobalDefaults.client.windowMonitoringBorder)
var maxs = Vector2i(windowWidth.get() + GlobalDefaults.client.windowMonitoringBorder, windowHeight.get() + GlobalDefaults.client.windowMonitoringBorder) var maxs = Vector2i(windowWidth + GlobalDefaults.client.windowMonitoringBorder, windowHeight + GlobalDefaults.client.windowMonitoringBorder)
if (maxs.x - mins.x > 1000) { if (maxs.x - mins.x > 1000) {
// holy shit // holy shit
@ -215,6 +238,9 @@ abstract class Connection(val side: ConnectionSide, val type: ConnectionType) :
companion object { companion object {
private val LOGGER = LogManager.getLogger() private val LOGGER = LogManager.getLogger()
private val warpActionCodec = StreamCodec.Pair(WarpAction.CODEC, WarpMode.CODEC).koptional()
private val legacyWarpActionCodec = StreamCodec.Pair(WarpAction.LEGACY_CODEC, WarpMode.CODEC).koptional()
val NIO_POOL by lazy { val NIO_POOL by lazy {
NioEventLoopGroup(1, ThreadFactoryBuilder().setDaemon(true).setNameFormat("Starbound Network IO %d").build()) NioEventLoopGroup(1, ThreadFactoryBuilder().setDaemon(true).setNameFormat("Starbound Network IO %d").build())
} }

View File

@ -33,6 +33,7 @@ import ru.dbotthepony.kstarbound.network.packets.ProtocolResponsePacket
import ru.dbotthepony.kstarbound.network.packets.StepUpdatePacket import ru.dbotthepony.kstarbound.network.packets.StepUpdatePacket
import ru.dbotthepony.kstarbound.network.packets.clientbound.CentralStructureUpdatePacket import ru.dbotthepony.kstarbound.network.packets.clientbound.CentralStructureUpdatePacket
import ru.dbotthepony.kstarbound.network.packets.clientbound.ChatReceivePacket import ru.dbotthepony.kstarbound.network.packets.clientbound.ChatReceivePacket
import ru.dbotthepony.kstarbound.network.packets.clientbound.ConnectFailurePacket
import ru.dbotthepony.kstarbound.network.packets.clientbound.FindUniqueEntityResponsePacket import ru.dbotthepony.kstarbound.network.packets.clientbound.FindUniqueEntityResponsePacket
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.LegacyTileUpdatePacket
@ -48,6 +49,7 @@ import ru.dbotthepony.kstarbound.network.packets.serverbound.ChatSendPacket
import ru.dbotthepony.kstarbound.network.packets.serverbound.ClientDisconnectRequestPacket import ru.dbotthepony.kstarbound.network.packets.serverbound.ClientDisconnectRequestPacket
import ru.dbotthepony.kstarbound.network.packets.serverbound.DamageTileGroupPacket import ru.dbotthepony.kstarbound.network.packets.serverbound.DamageTileGroupPacket
import ru.dbotthepony.kstarbound.network.packets.serverbound.FindUniqueEntityPacket import ru.dbotthepony.kstarbound.network.packets.serverbound.FindUniqueEntityPacket
import ru.dbotthepony.kstarbound.network.packets.serverbound.PlayerWarpPacket
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 java.io.BufferedInputStream import java.io.BufferedInputStream
@ -382,7 +384,7 @@ class PacketRegistry(val isLegacy: Boolean) {
// Packets sent universe server -> universe client // Packets sent universe server -> universe client
LEGACY.add(::ServerDisconnectPacket) // ServerDisconnect LEGACY.add(::ServerDisconnectPacket) // ServerDisconnect
LEGACY.add(::ConnectSuccessPacket) // ConnectSuccess LEGACY.add(::ConnectSuccessPacket) // ConnectSuccess
LEGACY.skip("ConnectFailure") LEGACY.add(::ConnectFailurePacket)
LEGACY.add(::HandshakeChallengePacket) // HandshakeChallenge LEGACY.add(::HandshakeChallengePacket) // HandshakeChallenge
LEGACY.add(::ChatReceivePacket) LEGACY.add(::ChatReceivePacket)
LEGACY.add(::UniverseTimeUpdatePacket) LEGACY.add(::UniverseTimeUpdatePacket)
@ -396,7 +398,7 @@ class PacketRegistry(val isLegacy: Boolean) {
LEGACY.add(::ClientConnectPacket) // ClientConnect LEGACY.add(::ClientConnectPacket) // ClientConnect
LEGACY.add(ClientDisconnectRequestPacket::read) LEGACY.add(ClientDisconnectRequestPacket::read)
LEGACY.add(::HandshakeResponsePacket) // HandshakeResponse LEGACY.add(::HandshakeResponsePacket) // HandshakeResponse
LEGACY.skip("PlayerWarp") LEGACY.add(::PlayerWarpPacket)
LEGACY.skip("FlyShip") LEGACY.skip("FlyShip")
LEGACY.add(::ChatSendPacket) LEGACY.add(::ChatSendPacket)
LEGACY.skip("CelestialRequest") LEGACY.skip("CelestialRequest")

View File

@ -7,6 +7,7 @@ import ru.dbotthepony.kommons.io.readByteArray
import ru.dbotthepony.kommons.io.readSignedVarInt import ru.dbotthepony.kommons.io.readSignedVarInt
import ru.dbotthepony.kommons.io.writeByteArray import ru.dbotthepony.kommons.io.writeByteArray
import ru.dbotthepony.kommons.io.writeSignedVarInt import ru.dbotthepony.kommons.io.writeSignedVarInt
import ru.dbotthepony.kstarbound.Starbound
import ru.dbotthepony.kstarbound.client.ClientConnection import ru.dbotthepony.kstarbound.client.ClientConnection
import ru.dbotthepony.kstarbound.defs.EntityType import ru.dbotthepony.kstarbound.defs.EntityType
import ru.dbotthepony.kstarbound.network.IClientPacket import ru.dbotthepony.kstarbound.network.IClientPacket

View File

@ -37,7 +37,7 @@ class EntityUpdateSetPacket(val forConnection: Int, val deltas: Int2ObjectMap<By
connection.disconnect("Updating entity with ID $id outside of allowed range ${connection.entityIDRange}") connection.disconnect("Updating entity with ID $id outside of allowed range ${connection.entityIDRange}")
break break
} else { } else {
entities[id]?.networkGroup?.read(delta, Starbound.TIMESTEP, connection.isLegacy) entities[id]?.networkGroup?.read(delta, Starbound.TIMESTEP * 3.0, connection.isLegacy)
} }
} }
} }

View File

@ -28,8 +28,7 @@ object PingPacket : IServerPacket {
} }
override fun play(connection: ServerConnection) { override fun play(connection: ServerConnection) {
// immediately respond to ping packets connection.send(PongPacket)
connection.sendAndFlush(PongPacket)
} }
fun read(stream: DataInputStream, isLegacy: Boolean): PingPacket { fun read(stream: DataInputStream, isLegacy: Boolean): PingPacket {

View File

@ -1,13 +1,13 @@
package ru.dbotthepony.kstarbound.network.packets.clientbound package ru.dbotthepony.kstarbound.network.packets.clientbound
import ru.dbotthepony.kstarbound.client.ClientConnection import ru.dbotthepony.kstarbound.client.ClientConnection
import ru.dbotthepony.kstarbound.defs.AbstractWarpTarget import ru.dbotthepony.kstarbound.defs.WarpAction
import ru.dbotthepony.kstarbound.network.IClientPacket import ru.dbotthepony.kstarbound.network.IClientPacket
import java.io.DataInputStream import java.io.DataInputStream
import java.io.DataOutputStream import java.io.DataOutputStream
class PlayerWarpResultPacket(val success: Boolean, val target: AbstractWarpTarget, val warpActionInvalid: Boolean) : IClientPacket { class PlayerWarpResultPacket(val success: Boolean, val target: WarpAction, val warpActionInvalid: Boolean) : IClientPacket {
constructor(stream: DataInputStream, isLegacy: Boolean) : this(stream.readBoolean(), AbstractWarpTarget.read(stream, isLegacy), stream.readBoolean()) constructor(stream: DataInputStream, isLegacy: Boolean) : this(stream.readBoolean(), WarpAction.read(stream, isLegacy), stream.readBoolean())
override fun write(stream: DataOutputStream, isLegacy: Boolean) { override fun write(stream: DataOutputStream, isLegacy: Boolean) {
stream.writeBoolean(success) stream.writeBoolean(success)

View File

@ -17,6 +17,7 @@ import ru.dbotthepony.kommons.io.writeUUID
import ru.dbotthepony.kommons.util.KOptional import ru.dbotthepony.kommons.util.KOptional
import ru.dbotthepony.kstarbound.defs.actor.player.ShipUpgrades import ru.dbotthepony.kstarbound.defs.actor.player.ShipUpgrades
import ru.dbotthepony.kstarbound.network.IServerPacket import ru.dbotthepony.kstarbound.network.IServerPacket
import ru.dbotthepony.kstarbound.network.packets.clientbound.ConnectFailurePacket
import ru.dbotthepony.kstarbound.network.packets.clientbound.ConnectSuccessPacket import ru.dbotthepony.kstarbound.network.packets.clientbound.ConnectSuccessPacket
import ru.dbotthepony.kstarbound.network.packets.clientbound.UniverseTimeUpdatePacket import ru.dbotthepony.kstarbound.network.packets.clientbound.UniverseTimeUpdatePacket
import ru.dbotthepony.kstarbound.server.ServerConnection import ru.dbotthepony.kstarbound.server.ServerConnection
@ -57,15 +58,24 @@ data class ClientConnectPacket(
} }
override fun play(connection: ServerConnection) { override fun play(connection: ServerConnection) {
LOGGER.info("Client connection request received from ${connection.channel.remoteAddress()}, Player $playerName/$playerUuid (account '$account')") if (connection.server.clientByUUID(playerUuid) != null) {
connection.send(ConnectFailurePacket("Duplicate player UUID $playerUuid"))
LOGGER.warn("Unable to accept player $playerName/$playerUuid because such UUID is already taken")
connection.channel.flush()
connection.close()
} else {
LOGGER.info("Client connection request received from ${connection.channel.remoteAddress()}, Player $playerName/$playerUuid (account '$account')")
connection.nickname = connection.server.reserveNickname(playerName, "Player_${connection.connectionID}") connection.nickname = connection.server.reserveNickname(playerName, "Player_${connection.connectionID}")
connection.shipUpgrades = shipUpgrades
connection.uuid = playerUuid
connection.receiveShipChunks(shipChunks) connection.receiveShipChunks(shipChunks)
connection.send(ConnectSuccessPacket(connection.connectionID, connection.server.serverUUID, connection.server.universe.baseInformation)) connection.send(ConnectSuccessPacket(connection.connectionID, connection.server.serverUUID, connection.server.universe.baseInformation))
connection.send(UniverseTimeUpdatePacket(connection.server.universeClock.seconds)) connection.send(UniverseTimeUpdatePacket(connection.server.universeClock.seconds))
connection.channel.flush() connection.channel.flush()
connection.inGame() connection.inGame()
}
} }
companion object { companion object {

View File

@ -0,0 +1,20 @@
package ru.dbotthepony.kstarbound.network.packets.serverbound
import ru.dbotthepony.kstarbound.defs.WarpAction
import ru.dbotthepony.kstarbound.network.IServerPacket
import ru.dbotthepony.kstarbound.server.ServerConnection
import java.io.DataInputStream
import java.io.DataOutputStream
class PlayerWarpPacket(val action: WarpAction, val deploy: Boolean) : IServerPacket {
constructor(stream: DataInputStream, isLegacy: Boolean) : this(WarpAction.read(stream, isLegacy), stream.readBoolean())
override fun write(stream: DataOutputStream, isLegacy: Boolean) {
action.write(stream, isLegacy)
stream.writeBoolean(deploy)
}
override fun play(connection: ServerConnection) {
connection.enqueueWarp(action, deploy)
}
}

View File

@ -18,6 +18,6 @@ class WorldClientStateUpdatePacket(val deltas: ByteArrayList) : IServerPacket {
} }
override fun play(connection: ServerConnection) { override fun play(connection: ServerConnection) {
connection.clientStateGroup.read(deltas.elements(), 0, deltas.size) connection.client2serverGroup.read(deltas.elements(), 0, deltas.size)
} }
} }

View File

@ -1,13 +1,20 @@
package ru.dbotthepony.kstarbound.server package ru.dbotthepony.kstarbound.server
import ru.dbotthepony.kstarbound.client.StarboundClient
import java.io.Closeable import java.io.Closeable
import java.io.File import java.io.File
class IntegratedStarboundServer(root: File) : StarboundServer(root), Closeable { class IntegratedStarboundServer(val client: StarboundClient, root: File) : StarboundServer(root), Closeable {
init { init {
channels.createLocalChannel() channels.createLocalChannel()
} }
override fun tick0() {
if (client.shouldTerminate) {
close()
}
}
override fun close0() { override fun close0() {
} }

View File

@ -2,12 +2,13 @@ package ru.dbotthepony.kstarbound.server
import com.google.gson.JsonObject 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.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.kstarbound.defs.WarpAction
import ru.dbotthepony.kstarbound.defs.WarpAlias import ru.dbotthepony.kstarbound.defs.WarpAlias
import ru.dbotthepony.kstarbound.defs.WorldID
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
@ -20,14 +21,19 @@ 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.ServerWorld import ru.dbotthepony.kstarbound.server.world.ServerWorld
import java.util.HashMap import java.util.HashMap
import java.util.UUID
import java.util.concurrent.CompletableFuture
import java.util.concurrent.ConcurrentLinkedQueue
import kotlin.properties.Delegates import kotlin.properties.Delegates
// serverside part of connection // serverside part of connection
class ServerConnection(val server: StarboundServer, type: ConnectionType) : Connection(ConnectionSide.SERVER, type) { class ServerConnection(val server: StarboundServer, type: ConnectionType) : Connection(ConnectionSide.SERVER, type) {
var world: ServerWorld? = null
var tracker: ServerWorldTracker? = null var tracker: ServerWorldTracker? = null
var worldStartAcknowledged = false var worldStartAcknowledged = false
val world: ServerWorld?
get() = tracker?.world
// packets which interact with world must be // packets which interact with world must be
// executed on world's thread // executed on world's thread
fun enqueue(task: ServerWorld.() -> Unit) = tracker?.enqueue(task) fun enqueue(task: ServerWorld.() -> Unit) = tracker?.enqueue(task)
@ -35,6 +41,8 @@ class ServerConnection(val server: StarboundServer, type: ConnectionType) : Conn
lateinit var shipWorld: ServerWorld lateinit var shipWorld: ServerWorld
private set private set
var uuid: UUID? = null
init { init {
connectionID = server.channels.nextConnectionID() connectionID = server.channels.nextConnectionID()
@ -69,15 +77,20 @@ class ServerConnection(val server: StarboundServer, type: ConnectionType) : Conn
shipChunks.putAll(chunks) shipChunks.putAll(chunks)
} }
private var remoteVersion = 0L
override fun flush() { override fun flush() {
if (isConnected) { if (isConnected) {
val entries = rpc.write() val entries = rpc.write()
if (entries != null || modifiedShipChunks.isNotEmpty()) { if (entries != null || modifiedShipChunks.isNotEmpty() || server2clientGroup.upstream.hasChangedSince(remoteVersion)) {
val (data, version) = server2clientGroup.write(remoteVersion, isLegacy)
remoteVersion = version
channel.write(ClientContextUpdatePacket( channel.write(ClientContextUpdatePacket(
entries ?: listOf(), entries ?: listOf(),
KOptional(modifiedShipChunks.associateWith { shipChunks[it]!! }), KOptional(modifiedShipChunks.associateWith { shipChunks[it]!! }),
KOptional(ByteArrayList()))) KOptional(data)))
modifiedShipChunks.clear() modifiedShipChunks.clear()
} }
@ -106,7 +119,59 @@ class ServerConnection(val server: StarboundServer, type: ConnectionType) : Conn
} }
} }
private var announcedDisconnect = false private var warpingAllowed = false
private var pendingWarp: Pair<WarpAction, Boolean>? = null
private var currentWarpStatus: CompletableFuture<*>? = null
fun enqueueWarp(destination: WarpAction, deploy: Boolean = false) {
pendingWarp = destination to deploy
}
fun tick() {
if (!isConnected || !channel.isOpen)
return
flush()
if (currentWarpStatus?.isDone == true)
currentWarpStatus = null
if (currentWarpStatus == null && warpingAllowed) {
val pendingWarp = pendingWarp
this.pendingWarp = null
if (pendingWarp != null) {
val (request, deploy) = pendingWarp
val resolve = request.resolve(this)
if (resolve.isLimbo) {
send(PlayerWarpResultPacket(false, request, true))
} else if (tracker?.world?.worldID == resolve) {
LOGGER.info("$this tried to warp into world they are already in.")
send(PlayerWarpResultPacket(true, request, false))
} else {
val world = server.worlds[resolve]
if (world == null) {
send(PlayerWarpResultPacket(false, request, false))
} else {
currentWarpStatus = world.acceptClient(this).exceptionally {
send(PlayerWarpResultPacket(false, request, false))
if (world == shipWorld) {
disconnect("ShipWorld refused to accept its owner: $it")
} else {
enqueueWarp(WarpAlias.OwnShip)
}
}
}
}
}
}
}
private var announcedDisconnect = true
private fun announceDisconnect(reason: String) { private fun announceDisconnect(reason: String) {
if (!announcedDisconnect && nickname.isNotBlank()) { if (!announcedDisconnect && nickname.isNotBlank()) {
@ -160,26 +225,19 @@ class ServerConnection(val server: StarboundServer, type: ConnectionType) : Conn
private var countedTowardsPlayerCount = false private var countedTowardsPlayerCount = false
override fun inGame() { override fun inGame() {
announcedDisconnect = false
server.chat.systemMessage("Player '$nickname' connected") server.chat.systemMessage("Player '$nickname' connected")
countedTowardsPlayerCount = true countedTowardsPlayerCount = true
server.channels.incrementPlayerCount() server.channels.incrementPlayerCount()
if (!isLegacy) { if (isLegacy) {
server.playerInGame(this)
} else {
LOGGER.info("Initializing ship world for $this") LOGGER.info("Initializing ship world for $this")
ServerWorld.load(server, shipChunkSource).thenAccept { ServerWorld.load(server, shipChunkSource, WorldID.ShipWorld(uuid!!)).thenAccept {
shipWorld = it shipWorld = it
shipWorld.thread.start() shipWorld.thread.start()
send(PlayerWarpResultPacket(true, WarpAlias.OwnShip, false)) enqueueWarp(WarpAlias.OwnShip)
warpingAllowed = true
//server.worlds.first().acceptPlayer(this)
shipWorld.acceptClient(this).exceptionally {
LOGGER.error("Shipworld of $this rejected to accept its owner", it)
disconnect("Shipworld rejected player warp request: $it")
}
}.exceptionally { }.exceptionally {
LOGGER.error("Error while initializing shipworld for $this", it) LOGGER.error("Error while initializing shipworld for $this", it)
disconnect("Error while initializing shipworld for player: $it") disconnect("Error while initializing shipworld for player: $it")

View File

@ -5,6 +5,7 @@ import org.apache.logging.log4j.LogManager
import ru.dbotthepony.kommons.util.MailboxExecutorService import ru.dbotthepony.kommons.util.MailboxExecutorService
import ru.dbotthepony.kstarbound.GlobalDefaults import ru.dbotthepony.kstarbound.GlobalDefaults
import ru.dbotthepony.kstarbound.Starbound import ru.dbotthepony.kstarbound.Starbound
import ru.dbotthepony.kstarbound.defs.WorldID
import ru.dbotthepony.kstarbound.network.packets.clientbound.UniverseTimeUpdatePacket import ru.dbotthepony.kstarbound.network.packets.clientbound.UniverseTimeUpdatePacket
import ru.dbotthepony.kstarbound.server.world.ServerUniverse import ru.dbotthepony.kstarbound.server.world.ServerUniverse
import ru.dbotthepony.kstarbound.server.world.ServerWorld import ru.dbotthepony.kstarbound.server.world.ServerWorld
@ -14,6 +15,7 @@ import ru.dbotthepony.kstarbound.util.ExecutionSpinner
import java.io.Closeable import java.io.Closeable
import java.io.File import java.io.File
import java.util.UUID import java.util.UUID
import java.util.concurrent.ConcurrentHashMap
import java.util.concurrent.CopyOnWriteArrayList import java.util.concurrent.CopyOnWriteArrayList
import java.util.concurrent.TimeUnit import java.util.concurrent.TimeUnit
import java.util.concurrent.atomic.AtomicInteger import java.util.concurrent.atomic.AtomicInteger
@ -28,11 +30,11 @@ sealed class StarboundServer(val root: File) : Closeable {
} }
} }
val worlds = CopyOnWriteArrayList<ServerWorld>() val worlds = ConcurrentHashMap<WorldID, ServerWorld>()
val serverID = threadCounter.getAndIncrement() val serverID = threadCounter.getAndIncrement()
val mailbox = MailboxExecutorService().also { it.exceptionHandler = ExceptionLogger(LOGGER) } val mailbox = MailboxExecutorService().also { it.exceptionHandler = ExceptionLogger(LOGGER) }
val spinner = ExecutionSpinner(mailbox::executeQueuedTasks, ::spin, Starbound.TIMESTEP_NANOS) val spinner = ExecutionSpinner(mailbox::executeQueuedTasks, ::tick, Starbound.TIMESTEP_NANOS)
val thread = Thread(spinner, "Starbound Server $serverID") val thread = Thread(spinner, "Server $serverID Thread")
val universe = ServerUniverse() val universe = ServerUniverse()
val chat = ChatHandler(this) val chat = ChatHandler(this)
@ -57,7 +59,7 @@ sealed class StarboundServer(val root: File) : Closeable {
actuallyClose() actuallyClose()
} }
thread.isDaemon = this is IntegratedStarboundServer // thread.isDaemon = this is IntegratedStarboundServer
thread.start() thread.start()
} }
@ -86,16 +88,26 @@ sealed class StarboundServer(val root: File) : Closeable {
} }
} }
fun playerInGame(player: ServerConnection) { fun clientByUUID(uuid: UUID): ServerConnection? {
val world = worlds.first() return channels.connections.firstOrNull { it.uuid == uuid }
world.acceptClient(player)
} }
protected abstract fun close0() protected abstract fun close0()
protected abstract fun tick0()
private fun spin(): Boolean { private fun tick(): Boolean {
if (isClosed) return false if (isClosed) return false
channels.connections.forEach { if (it.channel.isOpen) it.flush() }
channels.connections.forEach {
try {
it.tick()
} catch (err: Throwable) {
LOGGER.error("Exception while ticking client connection", err)
it.disconnect("Exception while ticking client connection: $err")
}
}
tick0()
return !isClosed return !isClosed
} }
@ -104,7 +116,7 @@ sealed class StarboundServer(val root: File) : Closeable {
isClosed = true isClosed = true
channels.close() channels.close()
worlds.forEach { it.close() } worlds.values.forEach { it.close() }
universe.close() universe.close()
close0() close0()
} }

View File

@ -9,6 +9,8 @@ import org.apache.logging.log4j.LogManager
import ru.dbotthepony.kommons.util.IStruct2i import ru.dbotthepony.kommons.util.IStruct2i
import ru.dbotthepony.kommons.vector.Vector2d import ru.dbotthepony.kommons.vector.Vector2d
import ru.dbotthepony.kstarbound.Starbound import ru.dbotthepony.kstarbound.Starbound
import ru.dbotthepony.kstarbound.defs.WarpAction
import ru.dbotthepony.kstarbound.defs.WorldID
import ru.dbotthepony.kstarbound.defs.tile.TileDamage import ru.dbotthepony.kstarbound.defs.tile.TileDamage
import ru.dbotthepony.kstarbound.defs.tile.TileDamageResult import ru.dbotthepony.kstarbound.defs.tile.TileDamageResult
import ru.dbotthepony.kstarbound.defs.world.WorldStructure import ru.dbotthepony.kstarbound.defs.world.WorldStructure
@ -16,6 +18,7 @@ import ru.dbotthepony.kstarbound.defs.world.WorldTemplate
import ru.dbotthepony.kstarbound.json.builder.JsonFactory import ru.dbotthepony.kstarbound.json.builder.JsonFactory
import ru.dbotthepony.kstarbound.network.IPacket import ru.dbotthepony.kstarbound.network.IPacket
import ru.dbotthepony.kstarbound.network.packets.StepUpdatePacket import ru.dbotthepony.kstarbound.network.packets.StepUpdatePacket
import ru.dbotthepony.kstarbound.network.packets.clientbound.PlayerWarpResultPacket
import ru.dbotthepony.kstarbound.server.StarboundServer import ru.dbotthepony.kstarbound.server.StarboundServer
import ru.dbotthepony.kstarbound.server.ServerConnection import ru.dbotthepony.kstarbound.server.ServerConnection
import ru.dbotthepony.kstarbound.util.AssetPathStack import ru.dbotthepony.kstarbound.util.AssetPathStack
@ -42,30 +45,49 @@ class ServerWorld private constructor(
val server: StarboundServer, val server: StarboundServer,
template: WorldTemplate, template: WorldTemplate,
val storage: WorldStorage, val storage: WorldStorage,
val worldID: WorldID,
) : World<ServerWorld, ServerChunk>(template) { ) : World<ServerWorld, ServerChunk>(template) {
init { init {
if (server.isClosed) if (server.isClosed)
throw RuntimeException() throw RuntimeException()
server.worlds.add(this) if (server.worlds.containsKey(worldID))
throw IllegalStateException("Duplicate world ID: $worldID")
server.worlds[worldID] = this
} }
val players = CopyOnWriteArrayList<ServerWorldTracker>() val players = CopyOnWriteArrayList<ServerWorldTracker>()
private fun doAcceptClient(client: ServerConnection) { private fun doAcceptClient(client: ServerConnection, action: WarpAction?) {
if (players.any { it.client == client }) if (players.any { it.client == client })
throw IllegalStateException("$client is already in $this") throw IllegalStateException("$client is already in $this")
val start = if (action is WarpAction.Player)
players.firstOrNull { it.client.uuid == action.uuid }?.client?.playerEntity?.position
else if (action is WarpAction.World)
action.target.resolve(this)
else
playerSpawnPosition
if (start == null) {
client.send(PlayerWarpResultPacket(false, action!!, true))
throw IllegalStateException("Not a valid spawn target: $action")
}
if (action != null)
client.send(PlayerWarpResultPacket(true, action, false))
client.tracker?.remove() client.tracker?.remove()
players.add(ServerWorldTracker(this, client)) players.add(ServerWorldTracker(this, client, start))
} }
fun acceptClient(player: ServerConnection): CompletableFuture<Unit> { fun acceptClient(player: ServerConnection, action: WarpAction? = null): CompletableFuture<Unit> {
check(!isClosed.get()) { "$this is invalid" } check(!isClosed.get()) { "$this is invalid" }
unpause() unpause()
try { try {
return CompletableFuture.supplyAsync(Supplier { doAcceptClient(player) }, mailbox).exceptionally { return CompletableFuture.supplyAsync(Supplier { doAcceptClient(player, action) }, mailbox).exceptionally {
LOGGER.error("Error while accepting new player into world", it) LOGGER.error("Error while accepting new player into world", it)
} }
} catch (err: RejectedExecutionException) { } catch (err: RejectedExecutionException) {
@ -74,7 +96,7 @@ class ServerWorld private constructor(
} }
val spinner = ExecutionSpinner(mailbox::executeQueuedTasks, ::spin, Starbound.TIMESTEP_NANOS) val spinner = ExecutionSpinner(mailbox::executeQueuedTasks, ::spin, Starbound.TIMESTEP_NANOS)
val thread = Thread(spinner, "Starbound Server World Thread") val thread = Thread(spinner, "Server World $worldID")
val ticketListLock = ReentrantLock() val ticketListLock = ReentrantLock()
private val isClosed = AtomicBoolean() private val isClosed = AtomicBoolean()
@ -91,6 +113,13 @@ class ServerWorld private constructor(
if (!isClosed.get()) spinner.unpause() if (!isClosed.get()) spinner.unpause()
} }
override fun toString(): String {
if (isClosed.get())
return "NULL ServerWorld at $worldID"
else
return "ServerWorld at $worldID"
}
override fun close() { override fun close() {
if (isClosed.compareAndSet(false, true)) { if (isClosed.compareAndSet(false, true)) {
LOGGER.info("Shutting down $this") LOGGER.info("Shutting down $this")
@ -98,7 +127,7 @@ class ServerWorld private constructor(
super.close() super.close()
spinner.unpause() spinner.unpause()
players.forEach { it.remove() } players.forEach { it.remove() }
server.worlds.remove(this) server.worlds.remove(worldID)
LockSupport.unpark(thread) LockSupport.unpark(thread)
} }
} }
@ -412,20 +441,20 @@ class ServerWorld private constructor(
companion object { companion object {
private val LOGGER = LogManager.getLogger() private val LOGGER = LogManager.getLogger()
fun create(server: StarboundServer, template: WorldTemplate, storage: WorldStorage): ServerWorld { fun create(server: StarboundServer, template: WorldTemplate, storage: WorldStorage, worldID: WorldID = WorldID.Limbo): ServerWorld {
return ServerWorld(server, template, storage) return ServerWorld(server, template, storage, worldID)
} }
fun create(server: StarboundServer, geometry: WorldGeometry, storage: WorldStorage): ServerWorld { fun create(server: StarboundServer, geometry: WorldGeometry, storage: WorldStorage, worldID: WorldID = WorldID.Limbo): ServerWorld {
return create(server, WorldTemplate(geometry), storage) return ServerWorld(server, WorldTemplate(geometry), storage, worldID)
} }
fun load(server: StarboundServer, storage: WorldStorage): CompletableFuture<ServerWorld> { fun load(server: StarboundServer, storage: WorldStorage, worldID: WorldID = WorldID.Limbo): CompletableFuture<ServerWorld> {
return storage.loadMetadata().thenApply { return storage.loadMetadata().thenApply {
AssetPathStack("/") { _ -> AssetPathStack("/") { _ ->
val meta = it.map { Starbound.gson.fromJson(it.data.content, MetadataJson::class.java) }.orThrow { NoSuchElementException("No world metadata is present") } val meta = it.map { Starbound.gson.fromJson(it.data.content, MetadataJson::class.java) }.orThrow { NoSuchElementException("No world metadata is present") }
val world = create(server, WorldTemplate.fromJson(meta.worldTemplate), storage) val world = ServerWorld(server, WorldTemplate.fromJson(meta.worldTemplate), storage, worldID)
world.playerSpawnPosition = meta.playerStart world.playerSpawnPosition = meta.playerStart
world.respawnInWorld = meta.respawnInWorld world.respawnInWorld = meta.respawnInWorld
world.adjustPlayerSpawn = meta.adjustPlayerStart world.adjustPlayerSpawn = meta.adjustPlayerStart

View File

@ -6,6 +6,7 @@ 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.ObjectArraySet import it.unimi.dsi.fastutil.objects.ObjectArraySet
import it.unimi.dsi.fastutil.objects.ObjectLinkedOpenHashSet import it.unimi.dsi.fastutil.objects.ObjectLinkedOpenHashSet
import ru.dbotthepony.kommons.vector.Vector2d
import ru.dbotthepony.kommons.vector.Vector2i import ru.dbotthepony.kommons.vector.Vector2i
import ru.dbotthepony.kstarbound.Starbound import ru.dbotthepony.kstarbound.Starbound
import ru.dbotthepony.kstarbound.client.network.packets.ChunkCellsPacket import ru.dbotthepony.kstarbound.client.network.packets.ChunkCellsPacket
@ -31,7 +32,7 @@ import java.util.concurrent.atomic.AtomicBoolean
// couples ServerWorld and ServerConnection together, // couples ServerWorld and ServerConnection together,
// allowing ServerConnection client to track ServerWorld state // allowing ServerConnection client to track ServerWorld state
class ServerWorldTracker(val world: ServerWorld, val client: ServerConnection) { class ServerWorldTracker(val world: ServerWorld, val client: ServerConnection, playerStart: Vector2d) {
init { init {
client.worldStartAcknowledged = false client.worldStartAcknowledged = false
client.tracker = this client.tracker = this
@ -82,7 +83,7 @@ class ServerWorldTracker(val world: ServerWorld, val client: ServerConnection) {
templateData = Starbound.writeLegacyJson { world.template.toJson() }, templateData = Starbound.writeLegacyJson { world.template.toJson() },
skyData = skyData.toByteArray(), skyData = skyData.toByteArray(),
weatherData = ByteArray(0), weatherData = ByteArray(0),
playerStart = world.playerSpawnPosition, playerStart = playerStart,
playerRespawn = world.playerSpawnPosition, playerRespawn = world.playerSpawnPosition,
respawnInWorld = world.respawnInWorld, respawnInWorld = world.respawnInWorld,
dungeonGravity = mapOf(), dungeonGravity = mapOf(),
@ -121,7 +122,7 @@ class ServerWorldTracker(val world: ServerWorld, val client: ServerConnection) {
} }
} }
client.playerEntity = world.entities[client.playerID.get()] as? PlayerEntity client.playerEntity = world.entities[client.playerID] as? PlayerEntity
run { run {
val newTrackedChunks = ObjectArraySet<ChunkPos>() val newTrackedChunks = ObjectArraySet<ChunkPos>()

View File

@ -12,8 +12,38 @@ import com.google.gson.stream.JsonWriter
import ru.dbotthepony.kommons.gson.get import ru.dbotthepony.kommons.gson.get
import ru.dbotthepony.kommons.vector.Vector3i import ru.dbotthepony.kommons.vector.Vector3i
import ru.dbotthepony.kommons.gson.consumeNull import ru.dbotthepony.kommons.gson.consumeNull
import ru.dbotthepony.kommons.io.readVarInt
import ru.dbotthepony.kommons.io.readVector3i
import ru.dbotthepony.kommons.io.writeSignedVarInt
import ru.dbotthepony.kommons.io.writeStruct3i
import ru.dbotthepony.kommons.io.writeVarInt
import ru.dbotthepony.kstarbound.network.syncher.legacyCodec
import ru.dbotthepony.kstarbound.network.syncher.nativeCodec
import java.io.DataInputStream
import java.io.DataOutputStream
/**
* Specifies coordinates to either a planetary system, a planetary body, or a
* satellite around such a planetary body. The terms here are meant to be very
* generic, a "planetary body" could be an asteroid field, or a ship, or
* anything in orbit around the center of mass of a specific planetary system.
* The terms are really simply meant as a hierarchy of orbits.
*
* No validity checking is done here, any coordinate to any body whether it
* exists in a specific universe or not can be expressed.
*/
data class UniversePos(val location: Vector3i = Vector3i.ZERO, val planetOrbit: Int = 0, val satelliteOrbit: Int = 0) { data class UniversePos(val location: Vector3i = Vector3i.ZERO, val planetOrbit: Int = 0, val satelliteOrbit: Int = 0) {
constructor(stream: DataInputStream, isLegacy: Boolean) : this(stream.readVector3i(), if (isLegacy) stream.readInt() else stream.readVarInt(), if (isLegacy) stream.readInt() else stream.readVarInt())
init {
require(planetOrbit >= 0) { "Negative planetOrbit: $planetOrbit" }
require(satelliteOrbit >= 0) { "Negative satelliteOrbit: $satelliteOrbit" }
}
override fun toString(): String {
return "UniversePos[$location, planetOrbit=$planetOrbit, satelliteOrbit=$satelliteOrbit]"
}
val isSystem: Boolean val isSystem: Boolean
get() = planetOrbit == 0 get() = planetOrbit == 0
@ -53,7 +83,22 @@ data class UniversePos(val location: Vector3i = Vector3i.ZERO, val planetOrbit:
return this return this
} }
fun write(stream: DataOutputStream, isLegacy: Boolean) {
stream.writeStruct3i(location)
if (isLegacy) {
stream.writeInt(planetOrbit)
stream.writeInt(satelliteOrbit)
} else {
stream.writeVarInt(planetOrbit)
stream.writeVarInt(satelliteOrbit)
}
}
companion object : TypeAdapterFactory { companion object : TypeAdapterFactory {
val CODEC = nativeCodec(::UniversePos, UniversePos::write)
val LEGACY_CODEC = legacyCodec(::UniversePos, UniversePos::write)
private val splitter = Regex("[ _:]") private val splitter = Regex("[ _:]")
val ZERO = UniversePos() val ZERO = UniversePos()