Player warping test code
This commit is contained in:
parent
f2c2db2e9d
commit
21a13134a4
@ -103,7 +103,7 @@ fun main() {
|
||||
Starbound.initializeGame()
|
||||
|
||||
Starbound.mailboxInitialized.submit {
|
||||
val server = IntegratedStarboundServer(File("./"))
|
||||
val server = IntegratedStarboundServer(client, File("./"))
|
||||
val world = ServerWorld.load(server, LegacyWorldStorage.file(db)).get()
|
||||
world.thread.start()
|
||||
|
||||
|
@ -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.WorldClientStateUpdatePacket
|
||||
import java.net.SocketAddress
|
||||
import java.util.*
|
||||
|
||||
// clientside part of connection
|
||||
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()))
|
||||
}
|
||||
|
||||
val (data, new) = clientStateGroup.write(clientStateNetVersion)
|
||||
val (data, new) = client2serverGroup.write(clientStateNetVersion)
|
||||
|
||||
if (data.isNotEmpty())
|
||||
channel.write(WorldClientStateUpdatePacket(data))
|
||||
|
@ -1,7 +1,23 @@
|
||||
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.DataOutputStream
|
||||
import java.util.UUID
|
||||
|
||||
// original game has MVariant here
|
||||
// MVariant prepends InvalidValue to Variant<> template
|
||||
@ -11,35 +27,190 @@ import java.io.DataOutputStream
|
||||
// -> Variant<InvalidType, WarpToWorld, WarpToPlayer, WarpAlias> WarpAction
|
||||
// hence WarpToWorld has index 1, WarpToPlayer 2, WarpAlias 3
|
||||
|
||||
sealed class AbstractWarpTarget {
|
||||
sealed class SpawnTarget {
|
||||
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 {
|
||||
fun read(stream: DataInputStream, isLegacy: Boolean): AbstractWarpTarget {
|
||||
return when (stream.readUnsignedByte()) {
|
||||
3 -> {
|
||||
when (stream.readInt()) {
|
||||
0 -> WarpAlias.Return
|
||||
1 -> WarpAlias.OrbitedWorld
|
||||
2 -> WarpAlias.OwnShip
|
||||
else -> throw IllegalArgumentException()
|
||||
}
|
||||
}
|
||||
|
||||
else -> throw IllegalArgumentException()
|
||||
fun read(stream: DataInputStream, isLegacy: Boolean): SpawnTarget {
|
||||
return when (val type = stream.readUnsignedByte()) {
|
||||
0 -> Whatever
|
||||
1 -> Entity(stream.readInternedString())
|
||||
2 -> Position(if (isLegacy) stream.readVector2f().toDoubleVector() else stream.readVector2d())
|
||||
3 -> X(if (isLegacy) stream.readFloat().toDouble() else stream.readDouble())
|
||||
else -> throw IllegalArgumentException("Unknown SpawnTarget type $type!")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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) {
|
||||
stream.write(3)
|
||||
// because it is defined as enum class WarpAlias, without specifying uint8_t as type
|
||||
stream.writeInt(index)
|
||||
}
|
||||
|
||||
object Return : WarpAlias(0)
|
||||
object OrbitedWorld : WarpAlias(1)
|
||||
object OwnShip : WarpAlias(2)
|
||||
object Return : WarpAlias(0) {
|
||||
override fun resolve(connection: ServerConnection): WorldID {
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
103
src/main/kotlin/ru/dbotthepony/kstarbound/defs/WorldID.kt
Normal file
103
src/main/kotlin/ru/dbotthepony/kstarbound/defs/WorldID.kt
Normal 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!")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -6,7 +6,10 @@ import ru.dbotthepony.kommons.io.readBinaryString
|
||||
import ru.dbotthepony.kommons.io.readCollection
|
||||
import ru.dbotthepony.kommons.io.writeBinaryString
|
||||
import ru.dbotthepony.kommons.io.writeCollection
|
||||
import ru.dbotthepony.kstarbound.io.readInternedString
|
||||
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.DataOutputStream
|
||||
|
||||
@ -25,7 +28,7 @@ data class ShipUpgrades(
|
||||
stream.readInt(),
|
||||
if (isLegacy) stream.readFloat().toDouble() else stream.readDouble(),
|
||||
stream.readInt(),
|
||||
ImmutableSet.copyOf(stream.readCollection { readBinaryString() })
|
||||
ImmutableSet.copyOf(stream.readCollection { readInternedString() })
|
||||
)
|
||||
|
||||
fun apply(upgrades: ShipUpgrades): ShipUpgrades {
|
||||
@ -52,4 +55,9 @@ data class ShipUpgrades(
|
||||
stream.writeInt(shipSpeed)
|
||||
stream.writeCollection(capabilities) { writeBinaryString(it) }
|
||||
}
|
||||
|
||||
companion object {
|
||||
val CODEC = nativeCodec(::ShipUpgrades, ShipUpgrades::write)
|
||||
val LEGACY_CODEC = legacyCodec(::ShipUpgrades, ShipUpgrades::write)
|
||||
}
|
||||
}
|
||||
|
@ -10,16 +10,28 @@ import it.unimi.dsi.fastutil.ints.IntAVLTreeSet
|
||||
import org.apache.logging.log4j.LogManager
|
||||
import ru.dbotthepony.kommons.io.StreamCodec
|
||||
import ru.dbotthepony.kommons.io.VarIntValueCodec
|
||||
import ru.dbotthepony.kommons.io.koptional
|
||||
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.Vector2i
|
||||
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.NetworkedGroup
|
||||
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.player.Avatar
|
||||
import ru.dbotthepony.kstarbound.server.ServerChannels
|
||||
import ru.dbotthepony.kstarbound.world.UniversePos
|
||||
import ru.dbotthepony.kstarbound.world.entities.player.PlayerEntity
|
||||
import java.io.Closeable
|
||||
import kotlin.math.roundToInt
|
||||
@ -150,24 +162,35 @@ abstract class Connection(val side: ConnectionSide, val type: ConnectionType) :
|
||||
channel.close()
|
||||
}
|
||||
|
||||
val windowXMin = networkedSignedInt()
|
||||
val windowYMin = networkedSignedInt()
|
||||
val windowWidth = networkedSignedInt()
|
||||
val windowHeight = networkedSignedInt()
|
||||
val playerID = networkedSignedInt()
|
||||
// global variables (per connection)
|
||||
// clientside variables
|
||||
val client2serverGroup = MasterElement(NetworkedGroup())
|
||||
var windowXMin by client2serverGroup.upstream.add(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
|
||||
|
||||
// holy shit
|
||||
val clientSpectatingEntities = BasicNetworkedElement(IntAVLTreeSet(), StreamCodec.Collection(VarIntValueCodec) { IntAVLTreeSet() })
|
||||
val clientStateGroup = MasterElement(NetworkedGroup(windowXMin, windowYMin, windowWidth, windowHeight, playerID, clientSpectatingEntities))
|
||||
|
||||
// in tiles
|
||||
fun trackingTileRegions(): List<AABBi> {
|
||||
val result = ArrayList<AABBi>()
|
||||
|
||||
var mins = Vector2i(windowXMin.get() - GlobalDefaults.client.windowMonitoringBorder, windowYMin.get() - GlobalDefaults.client.windowMonitoringBorder)
|
||||
var maxs = Vector2i(windowWidth.get() + GlobalDefaults.client.windowMonitoringBorder, windowHeight.get() + GlobalDefaults.client.windowMonitoringBorder)
|
||||
var mins = Vector2i(windowXMin - GlobalDefaults.client.windowMonitoringBorder, windowYMin - GlobalDefaults.client.windowMonitoringBorder)
|
||||
var maxs = Vector2i(windowWidth + GlobalDefaults.client.windowMonitoringBorder, windowHeight + GlobalDefaults.client.windowMonitoringBorder)
|
||||
|
||||
if (maxs.x - mins.x > 1000) {
|
||||
// holy shit
|
||||
@ -215,6 +238,9 @@ abstract class Connection(val side: ConnectionSide, val type: ConnectionType) :
|
||||
companion object {
|
||||
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 {
|
||||
NioEventLoopGroup(1, ThreadFactoryBuilder().setDaemon(true).setNameFormat("Starbound Network IO %d").build())
|
||||
}
|
||||
|
@ -33,6 +33,7 @@ import ru.dbotthepony.kstarbound.network.packets.ProtocolResponsePacket
|
||||
import ru.dbotthepony.kstarbound.network.packets.StepUpdatePacket
|
||||
import ru.dbotthepony.kstarbound.network.packets.clientbound.CentralStructureUpdatePacket
|
||||
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.LegacyTileArrayUpdatePacket
|
||||
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.DamageTileGroupPacket
|
||||
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.WorldStartAcknowledgePacket
|
||||
import java.io.BufferedInputStream
|
||||
@ -382,7 +384,7 @@ class PacketRegistry(val isLegacy: Boolean) {
|
||||
// Packets sent universe server -> universe client
|
||||
LEGACY.add(::ServerDisconnectPacket) // ServerDisconnect
|
||||
LEGACY.add(::ConnectSuccessPacket) // ConnectSuccess
|
||||
LEGACY.skip("ConnectFailure")
|
||||
LEGACY.add(::ConnectFailurePacket)
|
||||
LEGACY.add(::HandshakeChallengePacket) // HandshakeChallenge
|
||||
LEGACY.add(::ChatReceivePacket)
|
||||
LEGACY.add(::UniverseTimeUpdatePacket)
|
||||
@ -396,7 +398,7 @@ class PacketRegistry(val isLegacy: Boolean) {
|
||||
LEGACY.add(::ClientConnectPacket) // ClientConnect
|
||||
LEGACY.add(ClientDisconnectRequestPacket::read)
|
||||
LEGACY.add(::HandshakeResponsePacket) // HandshakeResponse
|
||||
LEGACY.skip("PlayerWarp")
|
||||
LEGACY.add(::PlayerWarpPacket)
|
||||
LEGACY.skip("FlyShip")
|
||||
LEGACY.add(::ChatSendPacket)
|
||||
LEGACY.skip("CelestialRequest")
|
||||
|
@ -7,6 +7,7 @@ import ru.dbotthepony.kommons.io.readByteArray
|
||||
import ru.dbotthepony.kommons.io.readSignedVarInt
|
||||
import ru.dbotthepony.kommons.io.writeByteArray
|
||||
import ru.dbotthepony.kommons.io.writeSignedVarInt
|
||||
import ru.dbotthepony.kstarbound.Starbound
|
||||
import ru.dbotthepony.kstarbound.client.ClientConnection
|
||||
import ru.dbotthepony.kstarbound.defs.EntityType
|
||||
import ru.dbotthepony.kstarbound.network.IClientPacket
|
||||
|
@ -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}")
|
||||
break
|
||||
} else {
|
||||
entities[id]?.networkGroup?.read(delta, Starbound.TIMESTEP, connection.isLegacy)
|
||||
entities[id]?.networkGroup?.read(delta, Starbound.TIMESTEP * 3.0, connection.isLegacy)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -28,8 +28,7 @@ object PingPacket : IServerPacket {
|
||||
}
|
||||
|
||||
override fun play(connection: ServerConnection) {
|
||||
// immediately respond to ping packets
|
||||
connection.sendAndFlush(PongPacket)
|
||||
connection.send(PongPacket)
|
||||
}
|
||||
|
||||
fun read(stream: DataInputStream, isLegacy: Boolean): PingPacket {
|
||||
|
@ -1,13 +1,13 @@
|
||||
package ru.dbotthepony.kstarbound.network.packets.clientbound
|
||||
|
||||
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 java.io.DataInputStream
|
||||
import java.io.DataOutputStream
|
||||
|
||||
class PlayerWarpResultPacket(val success: Boolean, val target: AbstractWarpTarget, val warpActionInvalid: Boolean) : IClientPacket {
|
||||
constructor(stream: DataInputStream, isLegacy: Boolean) : this(stream.readBoolean(), AbstractWarpTarget.read(stream, isLegacy), stream.readBoolean())
|
||||
class PlayerWarpResultPacket(val success: Boolean, val target: WarpAction, val warpActionInvalid: Boolean) : IClientPacket {
|
||||
constructor(stream: DataInputStream, isLegacy: Boolean) : this(stream.readBoolean(), WarpAction.read(stream, isLegacy), stream.readBoolean())
|
||||
|
||||
override fun write(stream: DataOutputStream, isLegacy: Boolean) {
|
||||
stream.writeBoolean(success)
|
||||
|
@ -17,6 +17,7 @@ import ru.dbotthepony.kommons.io.writeUUID
|
||||
import ru.dbotthepony.kommons.util.KOptional
|
||||
import ru.dbotthepony.kstarbound.defs.actor.player.ShipUpgrades
|
||||
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.UniverseTimeUpdatePacket
|
||||
import ru.dbotthepony.kstarbound.server.ServerConnection
|
||||
@ -57,15 +58,24 @@ data class ClientConnectPacket(
|
||||
}
|
||||
|
||||
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.send(ConnectSuccessPacket(connection.connectionID, connection.server.serverUUID, connection.server.universe.baseInformation))
|
||||
connection.send(UniverseTimeUpdatePacket(connection.server.universeClock.seconds))
|
||||
connection.channel.flush()
|
||||
connection.inGame()
|
||||
connection.receiveShipChunks(shipChunks)
|
||||
connection.send(ConnectSuccessPacket(connection.connectionID, connection.server.serverUUID, connection.server.universe.baseInformation))
|
||||
connection.send(UniverseTimeUpdatePacket(connection.server.universeClock.seconds))
|
||||
connection.channel.flush()
|
||||
connection.inGame()
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
@ -18,6 +18,6 @@ class WorldClientStateUpdatePacket(val deltas: ByteArrayList) : IServerPacket {
|
||||
}
|
||||
|
||||
override fun play(connection: ServerConnection) {
|
||||
connection.clientStateGroup.read(deltas.elements(), 0, deltas.size)
|
||||
connection.client2serverGroup.read(deltas.elements(), 0, deltas.size)
|
||||
}
|
||||
}
|
||||
|
@ -1,13 +1,20 @@
|
||||
package ru.dbotthepony.kstarbound.server
|
||||
|
||||
import ru.dbotthepony.kstarbound.client.StarboundClient
|
||||
import java.io.Closeable
|
||||
import java.io.File
|
||||
|
||||
class IntegratedStarboundServer(root: File) : StarboundServer(root), Closeable {
|
||||
class IntegratedStarboundServer(val client: StarboundClient, root: File) : StarboundServer(root), Closeable {
|
||||
init {
|
||||
channels.createLocalChannel()
|
||||
}
|
||||
|
||||
override fun tick0() {
|
||||
if (client.shouldTerminate) {
|
||||
close()
|
||||
}
|
||||
}
|
||||
|
||||
override fun close0() {
|
||||
|
||||
}
|
||||
|
@ -2,12 +2,13 @@ package ru.dbotthepony.kstarbound.server
|
||||
|
||||
import com.google.gson.JsonObject
|
||||
import io.netty.channel.ChannelHandlerContext
|
||||
import it.unimi.dsi.fastutil.bytes.ByteArrayList
|
||||
import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet
|
||||
import org.apache.logging.log4j.LogManager
|
||||
import ru.dbotthepony.kommons.io.ByteKey
|
||||
import ru.dbotthepony.kommons.util.KOptional
|
||||
import ru.dbotthepony.kstarbound.defs.WarpAction
|
||||
import ru.dbotthepony.kstarbound.defs.WarpAlias
|
||||
import ru.dbotthepony.kstarbound.defs.WorldID
|
||||
import ru.dbotthepony.kstarbound.network.Connection
|
||||
import ru.dbotthepony.kstarbound.network.ConnectionSide
|
||||
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.ServerWorld
|
||||
import java.util.HashMap
|
||||
import java.util.UUID
|
||||
import java.util.concurrent.CompletableFuture
|
||||
import java.util.concurrent.ConcurrentLinkedQueue
|
||||
import kotlin.properties.Delegates
|
||||
|
||||
// serverside part of connection
|
||||
class ServerConnection(val server: StarboundServer, type: ConnectionType) : Connection(ConnectionSide.SERVER, type) {
|
||||
var world: ServerWorld? = null
|
||||
var tracker: ServerWorldTracker? = null
|
||||
var worldStartAcknowledged = false
|
||||
|
||||
val world: ServerWorld?
|
||||
get() = tracker?.world
|
||||
|
||||
// packets which interact with world must be
|
||||
// executed on world's thread
|
||||
fun enqueue(task: ServerWorld.() -> Unit) = tracker?.enqueue(task)
|
||||
@ -35,6 +41,8 @@ class ServerConnection(val server: StarboundServer, type: ConnectionType) : Conn
|
||||
lateinit var shipWorld: ServerWorld
|
||||
private set
|
||||
|
||||
var uuid: UUID? = null
|
||||
|
||||
init {
|
||||
connectionID = server.channels.nextConnectionID()
|
||||
|
||||
@ -69,15 +77,20 @@ class ServerConnection(val server: StarboundServer, type: ConnectionType) : Conn
|
||||
shipChunks.putAll(chunks)
|
||||
}
|
||||
|
||||
private var remoteVersion = 0L
|
||||
|
||||
override fun flush() {
|
||||
if (isConnected) {
|
||||
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(
|
||||
entries ?: listOf(),
|
||||
KOptional(modifiedShipChunks.associateWith { shipChunks[it]!! }),
|
||||
KOptional(ByteArrayList())))
|
||||
KOptional(data)))
|
||||
|
||||
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) {
|
||||
if (!announcedDisconnect && nickname.isNotBlank()) {
|
||||
@ -160,26 +225,19 @@ class ServerConnection(val server: StarboundServer, type: ConnectionType) : Conn
|
||||
private var countedTowardsPlayerCount = false
|
||||
|
||||
override fun inGame() {
|
||||
announcedDisconnect = false
|
||||
server.chat.systemMessage("Player '$nickname' connected")
|
||||
countedTowardsPlayerCount = true
|
||||
server.channels.incrementPlayerCount()
|
||||
|
||||
if (!isLegacy) {
|
||||
server.playerInGame(this)
|
||||
} else {
|
||||
if (isLegacy) {
|
||||
LOGGER.info("Initializing ship world for $this")
|
||||
|
||||
ServerWorld.load(server, shipChunkSource).thenAccept {
|
||||
ServerWorld.load(server, shipChunkSource, WorldID.ShipWorld(uuid!!)).thenAccept {
|
||||
shipWorld = it
|
||||
shipWorld.thread.start()
|
||||
send(PlayerWarpResultPacket(true, WarpAlias.OwnShip, false))
|
||||
|
||||
//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")
|
||||
}
|
||||
enqueueWarp(WarpAlias.OwnShip)
|
||||
warpingAllowed = true
|
||||
}.exceptionally {
|
||||
LOGGER.error("Error while initializing shipworld for $this", it)
|
||||
disconnect("Error while initializing shipworld for player: $it")
|
||||
|
@ -5,6 +5,7 @@ import org.apache.logging.log4j.LogManager
|
||||
import ru.dbotthepony.kommons.util.MailboxExecutorService
|
||||
import ru.dbotthepony.kstarbound.GlobalDefaults
|
||||
import ru.dbotthepony.kstarbound.Starbound
|
||||
import ru.dbotthepony.kstarbound.defs.WorldID
|
||||
import ru.dbotthepony.kstarbound.network.packets.clientbound.UniverseTimeUpdatePacket
|
||||
import ru.dbotthepony.kstarbound.server.world.ServerUniverse
|
||||
import ru.dbotthepony.kstarbound.server.world.ServerWorld
|
||||
@ -14,6 +15,7 @@ import ru.dbotthepony.kstarbound.util.ExecutionSpinner
|
||||
import java.io.Closeable
|
||||
import java.io.File
|
||||
import java.util.UUID
|
||||
import java.util.concurrent.ConcurrentHashMap
|
||||
import java.util.concurrent.CopyOnWriteArrayList
|
||||
import java.util.concurrent.TimeUnit
|
||||
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 mailbox = MailboxExecutorService().also { it.exceptionHandler = ExceptionLogger(LOGGER) }
|
||||
val spinner = ExecutionSpinner(mailbox::executeQueuedTasks, ::spin, Starbound.TIMESTEP_NANOS)
|
||||
val thread = Thread(spinner, "Starbound Server $serverID")
|
||||
val spinner = ExecutionSpinner(mailbox::executeQueuedTasks, ::tick, Starbound.TIMESTEP_NANOS)
|
||||
val thread = Thread(spinner, "Server $serverID Thread")
|
||||
val universe = ServerUniverse()
|
||||
val chat = ChatHandler(this)
|
||||
|
||||
@ -57,7 +59,7 @@ sealed class StarboundServer(val root: File) : Closeable {
|
||||
actuallyClose()
|
||||
}
|
||||
|
||||
thread.isDaemon = this is IntegratedStarboundServer
|
||||
// thread.isDaemon = this is IntegratedStarboundServer
|
||||
thread.start()
|
||||
}
|
||||
|
||||
@ -86,16 +88,26 @@ sealed class StarboundServer(val root: File) : Closeable {
|
||||
}
|
||||
}
|
||||
|
||||
fun playerInGame(player: ServerConnection) {
|
||||
val world = worlds.first()
|
||||
world.acceptClient(player)
|
||||
fun clientByUUID(uuid: UUID): ServerConnection? {
|
||||
return channels.connections.firstOrNull { it.uuid == uuid }
|
||||
}
|
||||
|
||||
protected abstract fun close0()
|
||||
protected abstract fun tick0()
|
||||
|
||||
private fun spin(): Boolean {
|
||||
private fun tick(): Boolean {
|
||||
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
|
||||
}
|
||||
|
||||
@ -104,7 +116,7 @@ sealed class StarboundServer(val root: File) : Closeable {
|
||||
isClosed = true
|
||||
|
||||
channels.close()
|
||||
worlds.forEach { it.close() }
|
||||
worlds.values.forEach { it.close() }
|
||||
universe.close()
|
||||
close0()
|
||||
}
|
||||
|
@ -9,6 +9,8 @@ import org.apache.logging.log4j.LogManager
|
||||
import ru.dbotthepony.kommons.util.IStruct2i
|
||||
import ru.dbotthepony.kommons.vector.Vector2d
|
||||
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.TileDamageResult
|
||||
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.network.IPacket
|
||||
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.ServerConnection
|
||||
import ru.dbotthepony.kstarbound.util.AssetPathStack
|
||||
@ -42,30 +45,49 @@ class ServerWorld private constructor(
|
||||
val server: StarboundServer,
|
||||
template: WorldTemplate,
|
||||
val storage: WorldStorage,
|
||||
val worldID: WorldID,
|
||||
) : World<ServerWorld, ServerChunk>(template) {
|
||||
init {
|
||||
if (server.isClosed)
|
||||
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>()
|
||||
|
||||
private fun doAcceptClient(client: ServerConnection) {
|
||||
private fun doAcceptClient(client: ServerConnection, action: WarpAction?) {
|
||||
if (players.any { it.client == client })
|
||||
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()
|
||||
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" }
|
||||
unpause()
|
||||
|
||||
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)
|
||||
}
|
||||
} catch (err: RejectedExecutionException) {
|
||||
@ -74,7 +96,7 @@ class ServerWorld private constructor(
|
||||
}
|
||||
|
||||
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()
|
||||
|
||||
private val isClosed = AtomicBoolean()
|
||||
@ -91,6 +113,13 @@ class ServerWorld private constructor(
|
||||
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() {
|
||||
if (isClosed.compareAndSet(false, true)) {
|
||||
LOGGER.info("Shutting down $this")
|
||||
@ -98,7 +127,7 @@ class ServerWorld private constructor(
|
||||
super.close()
|
||||
spinner.unpause()
|
||||
players.forEach { it.remove() }
|
||||
server.worlds.remove(this)
|
||||
server.worlds.remove(worldID)
|
||||
LockSupport.unpark(thread)
|
||||
}
|
||||
}
|
||||
@ -412,20 +441,20 @@ class ServerWorld private constructor(
|
||||
companion object {
|
||||
private val LOGGER = LogManager.getLogger()
|
||||
|
||||
fun create(server: StarboundServer, template: WorldTemplate, storage: WorldStorage): ServerWorld {
|
||||
return ServerWorld(server, template, storage)
|
||||
fun create(server: StarboundServer, template: WorldTemplate, storage: WorldStorage, worldID: WorldID = WorldID.Limbo): ServerWorld {
|
||||
return ServerWorld(server, template, storage, worldID)
|
||||
}
|
||||
|
||||
fun create(server: StarboundServer, geometry: WorldGeometry, storage: WorldStorage): ServerWorld {
|
||||
return create(server, WorldTemplate(geometry), storage)
|
||||
fun create(server: StarboundServer, geometry: WorldGeometry, storage: WorldStorage, worldID: WorldID = WorldID.Limbo): ServerWorld {
|
||||
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 {
|
||||
AssetPathStack("/") { _ ->
|
||||
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.respawnInWorld = meta.respawnInWorld
|
||||
world.adjustPlayerSpawn = meta.adjustPlayerStart
|
||||
|
@ -6,6 +6,7 @@ import it.unimi.dsi.fastutil.ints.Int2ObjectMaps
|
||||
import it.unimi.dsi.fastutil.io.FastByteArrayOutputStream
|
||||
import it.unimi.dsi.fastutil.objects.ObjectArraySet
|
||||
import it.unimi.dsi.fastutil.objects.ObjectLinkedOpenHashSet
|
||||
import ru.dbotthepony.kommons.vector.Vector2d
|
||||
import ru.dbotthepony.kommons.vector.Vector2i
|
||||
import ru.dbotthepony.kstarbound.Starbound
|
||||
import ru.dbotthepony.kstarbound.client.network.packets.ChunkCellsPacket
|
||||
@ -31,7 +32,7 @@ import java.util.concurrent.atomic.AtomicBoolean
|
||||
|
||||
// couples ServerWorld and ServerConnection together,
|
||||
// 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 {
|
||||
client.worldStartAcknowledged = false
|
||||
client.tracker = this
|
||||
@ -82,7 +83,7 @@ class ServerWorldTracker(val world: ServerWorld, val client: ServerConnection) {
|
||||
templateData = Starbound.writeLegacyJson { world.template.toJson() },
|
||||
skyData = skyData.toByteArray(),
|
||||
weatherData = ByteArray(0),
|
||||
playerStart = world.playerSpawnPosition,
|
||||
playerStart = playerStart,
|
||||
playerRespawn = world.playerSpawnPosition,
|
||||
respawnInWorld = world.respawnInWorld,
|
||||
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 {
|
||||
val newTrackedChunks = ObjectArraySet<ChunkPos>()
|
||||
|
@ -12,8 +12,38 @@ import com.google.gson.stream.JsonWriter
|
||||
import ru.dbotthepony.kommons.gson.get
|
||||
import ru.dbotthepony.kommons.vector.Vector3i
|
||||
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) {
|
||||
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
|
||||
get() = planetOrbit == 0
|
||||
|
||||
@ -53,7 +83,22 @@ data class UniversePos(val location: Vector3i = Vector3i.ZERO, val planetOrbit:
|
||||
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 {
|
||||
val CODEC = nativeCodec(::UniversePos, UniversePos::write)
|
||||
val LEGACY_CODEC = legacyCodec(::UniversePos, UniversePos::write)
|
||||
|
||||
private val splitter = Regex("[ _:]")
|
||||
val ZERO = UniversePos()
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user