SystemWorld, fixed MWCRandom, event loops, universe io
This commit is contained in:
parent
3b2e1d06c3
commit
db09de857b
@ -3,6 +3,7 @@ package ru.dbotthepony.kstarbound
|
||||
import com.google.common.collect.ImmutableMap
|
||||
import com.google.gson.Gson
|
||||
import com.google.gson.GsonBuilder
|
||||
import com.google.gson.JsonElement
|
||||
import com.google.gson.TypeAdapter
|
||||
import com.google.gson.TypeAdapterFactory
|
||||
import com.google.gson.reflect.TypeToken
|
||||
@ -48,6 +49,7 @@ operator fun <K, V> ImmutableMap.Builder<K, V>.set(key: K, value: V): ImmutableM
|
||||
fun String.sintern(): String = Starbound.STRINGS.intern(this)
|
||||
|
||||
inline fun <reified T> Gson.fromJson(reader: JsonReader): T? = fromJson<T>(reader, T::class.java)
|
||||
inline fun <reified T> Gson.fromJson(reader: JsonElement): T? = fromJson<T>(reader, T::class.java)
|
||||
|
||||
/**
|
||||
* guarantees even distribution of tasks while also preserving encountered order of elements
|
||||
|
@ -14,6 +14,8 @@ import ru.dbotthepony.kstarbound.defs.world.TerrestrialWorldsConfig
|
||||
import ru.dbotthepony.kstarbound.defs.world.AsteroidWorldsConfig
|
||||
import ru.dbotthepony.kstarbound.defs.world.DungeonWorldsConfig
|
||||
import ru.dbotthepony.kstarbound.defs.world.SkyGlobalConfig
|
||||
import ru.dbotthepony.kstarbound.defs.world.SystemWorldConfig
|
||||
import ru.dbotthepony.kstarbound.defs.world.SystemWorldObjectConfig
|
||||
import ru.dbotthepony.kstarbound.defs.world.WorldTemplateConfig
|
||||
import ru.dbotthepony.kstarbound.json.mapAdapter
|
||||
import ru.dbotthepony.kstarbound.util.AssetPathStack
|
||||
@ -70,6 +72,12 @@ object GlobalDefaults {
|
||||
var currencies by Delegates.notNull<ImmutableMap<String, CurrencyDefinition>>()
|
||||
private set
|
||||
|
||||
var systemObjects by Delegates.notNull<ImmutableMap<String, SystemWorldObjectConfig>>()
|
||||
private set
|
||||
|
||||
var systemWorld by Delegates.notNull<SystemWorldConfig>()
|
||||
private set
|
||||
|
||||
private object EmptyTask : ForkJoinTask<Unit>() {
|
||||
private fun readResolve(): Any = EmptyTask
|
||||
override fun getRawResult() {
|
||||
@ -119,6 +127,7 @@ object GlobalDefaults {
|
||||
tasks.add(load("/sky.config", ::sky))
|
||||
tasks.add(load("/universe_server.config", ::universeServer))
|
||||
tasks.add(load("/player.config", ::player))
|
||||
tasks.add(load("/systemworld.config", ::systemWorld))
|
||||
|
||||
tasks.add(load("/plants/grassDamage.config", ::grassDamage))
|
||||
tasks.add(load("/plants/treeDamage.config", ::treeDamage))
|
||||
@ -127,6 +136,7 @@ object GlobalDefaults {
|
||||
|
||||
tasks.add(load("/dungeon_worlds.config", ::dungeonWorlds, Starbound.gson.mapAdapter()))
|
||||
tasks.add(load("/currencies.config", ::currencies, Starbound.gson.mapAdapter()))
|
||||
tasks.add(load("/system_objects.config", ::systemObjects, Starbound.gson.mapAdapter()))
|
||||
|
||||
return tasks
|
||||
}
|
||||
|
@ -13,6 +13,7 @@ import ru.dbotthepony.kstarbound.server.IntegratedStarboundServer
|
||||
import ru.dbotthepony.kstarbound.server.world.LegacyWorldStorage
|
||||
import ru.dbotthepony.kstarbound.server.world.ServerUniverse
|
||||
import ru.dbotthepony.kstarbound.server.world.ServerWorld
|
||||
import ru.dbotthepony.kstarbound.util.random.random
|
||||
import ru.dbotthepony.kstarbound.util.random.staticRandomDouble
|
||||
import ru.dbotthepony.kstarbound.world.WorldGeometry
|
||||
import java.io.BufferedInputStream
|
||||
@ -34,7 +35,7 @@ fun main() {
|
||||
|
||||
val t = System.nanoTime()
|
||||
val result = Starbound.COROUTINES.future {
|
||||
val systems = data.scanSystems(AABBi(Vector2i(-50, -50), Vector2i(50, 50)), setOf("whitestar"))
|
||||
val systems = data.findSystems(AABBi(Vector2i(-50, -50), Vector2i(50, 50)), setOf("whitestar"))
|
||||
|
||||
for (system in systems) {
|
||||
for (children in data.children(system)) {
|
||||
|
@ -408,6 +408,10 @@ object Starbound : ISBFileLocator {
|
||||
private set
|
||||
var loadingProgress = 0.0
|
||||
private set
|
||||
var toLoad = 0
|
||||
private set
|
||||
var loaded = 0
|
||||
private set
|
||||
|
||||
@Volatile
|
||||
var terminateLoading = false
|
||||
@ -571,10 +575,12 @@ object Starbound : ISBFileLocator {
|
||||
tasks.add(VersionRegistry.load())
|
||||
|
||||
val total = tasks.size.toDouble()
|
||||
toLoad = tasks.size
|
||||
|
||||
while (tasks.isNotEmpty()) {
|
||||
tasks.removeIf { it.isDone }
|
||||
checkMailbox()
|
||||
loaded = toLoad - tasks.size
|
||||
loadingProgress = (total - tasks.size) / total
|
||||
LockSupport.parkNanos(5_000_000L)
|
||||
}
|
||||
|
@ -231,7 +231,7 @@ class StarboundClient private constructor(val clientID: Int) : Closeable {
|
||||
GLFW.glfwWindowHint(GLFW.GLFW_OPENGL_PROFILE, GLFW.GLFW_OPENGL_CORE_PROFILE)
|
||||
GLFW.glfwWindowHint(GLFW.GLFW_OPENGL_FORWARD_COMPAT, GLFW.GLFW_TRUE)
|
||||
|
||||
window = GLFW.glfwCreateWindow(800, 600, "KStarbound", MemoryUtil.NULL, MemoryUtil.NULL)
|
||||
window = GLFW.glfwCreateWindow(800, 600, "KStarbound: Locating files...", MemoryUtil.NULL, MemoryUtil.NULL)
|
||||
require(window != MemoryUtil.NULL) { "Unable to create GLFW window" }
|
||||
|
||||
input.installCallback(window)
|
||||
@ -760,6 +760,8 @@ class StarboundClient private constructor(val clientID: Int) : Closeable {
|
||||
if (!onlyMemory) font.render("OGL C: $openglObjectsCreated D: $openglObjectsCleaned A: ${openglObjectsCreated - openglObjectsCleaned}", y = font.lineHeight * 1.8f, scale = 0.4f)
|
||||
}
|
||||
|
||||
private var renderedLoadingScreen = false
|
||||
|
||||
private fun renderLoadingScreen() {
|
||||
executeQueuedTasks()
|
||||
updateViewportParams()
|
||||
@ -805,6 +807,9 @@ class StarboundClient private constructor(val clientID: Int) : Closeable {
|
||||
|
||||
builder.builder.quad(0f, viewportHeight - 20f, viewportWidth * Starbound.loadingProgress.toFloat(), viewportHeight.toFloat()) { color(RGBAColor.GREEN) }
|
||||
|
||||
GLFW.glfwSetWindowTitle(window, "KStarbound: Loading JSON assets ${Starbound.loaded} / ${Starbound.toLoad}")
|
||||
renderedLoadingScreen = true
|
||||
|
||||
val runtime = Runtime.getRuntime()
|
||||
|
||||
//if (runtime.maxMemory() <= 4L * 1024L * 1024L * 1024L) {
|
||||
@ -925,6 +930,11 @@ class StarboundClient private constructor(val clientID: Int) : Closeable {
|
||||
return true
|
||||
}
|
||||
|
||||
if (renderedLoadingScreen) {
|
||||
renderedLoadingScreen = false
|
||||
GLFW.glfwSetWindowTitle(window, "KStarbound")
|
||||
}
|
||||
|
||||
input.think()
|
||||
camera.think(Starbound.TIMESTEP)
|
||||
executeQueuedTasks()
|
||||
|
@ -0,0 +1,47 @@
|
||||
package ru.dbotthepony.kstarbound.defs
|
||||
|
||||
import com.google.gson.JsonElement
|
||||
import com.google.gson.JsonNull
|
||||
import ru.dbotthepony.kstarbound.json.builder.IStringSerializable
|
||||
import ru.dbotthepony.kstarbound.json.readJsonElement
|
||||
import ru.dbotthepony.kstarbound.json.writeJsonElement
|
||||
import java.io.DataInputStream
|
||||
import java.io.DataOutputStream
|
||||
|
||||
data class InteractAction(val type: Type = Type.NONE, val entityID: Int = 0, val data: JsonElement = JsonNull.INSTANCE) {
|
||||
// int32_t
|
||||
enum class Type(override val jsonName: String) : IStringSerializable {
|
||||
NONE("None"),
|
||||
OPEN_CONTAINER("OpenContainer"),
|
||||
SIT_DOWN("SitDown"),
|
||||
OPEN_CRAFTING_INTERFACE("OpenCraftingInterface"),
|
||||
OPEN_SONGBOOK_INTERFACE("OpenSongbookInterface"),
|
||||
OPEN_NPC_CRAFTING_INTERFACE("OpenNpcCraftingInterface"),
|
||||
OPEN_MERCHANT_INTERFACE("OpenMerchantInterface"),
|
||||
OPEN_AI_INTERFACE("OpenAiInterface"),
|
||||
OPEN_TELEPORT_DIALOG("OpenTeleportDialog"),
|
||||
SHOW_POPUP("ShowPopup"),
|
||||
SCRIPT_PANE("ScriptPane"),
|
||||
MESSAGE("Message");
|
||||
}
|
||||
|
||||
constructor(type: String, entityID: Int = 0, data: JsonElement = JsonNull.INSTANCE) : this(
|
||||
Type.entries.firstOrNull { it.jsonName == type } ?: throw NoSuchElementException("No such interaction action $type!"), entityID, data
|
||||
)
|
||||
|
||||
constructor(stream: DataInputStream, isLegacy: Boolean) : this(
|
||||
if (isLegacy) Type.entries[stream.readInt()] else Type.entries[stream.readUnsignedByte()],
|
||||
stream.readInt(),
|
||||
stream.readJsonElement()
|
||||
)
|
||||
|
||||
fun write(stream: DataOutputStream, isLegacy: Boolean) {
|
||||
if (isLegacy) stream.writeInt(type.ordinal) else stream.writeByte(type.ordinal)
|
||||
stream.writeInt(entityID)
|
||||
stream.writeJsonElement(data)
|
||||
}
|
||||
|
||||
companion object {
|
||||
val NONE = InteractAction()
|
||||
}
|
||||
}
|
@ -0,0 +1,23 @@
|
||||
package ru.dbotthepony.kstarbound.defs
|
||||
|
||||
import ru.dbotthepony.kommons.vector.Vector2d
|
||||
import ru.dbotthepony.kstarbound.io.readVector2d
|
||||
import ru.dbotthepony.kstarbound.io.writeStruct2d
|
||||
import java.io.DataInputStream
|
||||
import java.io.DataOutputStream
|
||||
|
||||
data class InteractRequest(val source: Int, val sourcePos: Vector2d, val target: Int, val targetPos: Vector2d) {
|
||||
constructor(stream: DataInputStream, isLegacy: Boolean) : this(
|
||||
stream.readInt(),
|
||||
stream.readVector2d(isLegacy),
|
||||
stream.readInt(),
|
||||
stream.readVector2d(isLegacy),
|
||||
)
|
||||
|
||||
fun write(stream: DataOutputStream, isLegacy: Boolean) {
|
||||
stream.writeInt(source)
|
||||
stream.writeStruct2d(sourcePos, isLegacy)
|
||||
stream.writeInt(target)
|
||||
stream.writeStruct2d(targetPos, isLegacy)
|
||||
}
|
||||
}
|
@ -1,5 +1,10 @@
|
||||
package ru.dbotthepony.kstarbound.defs
|
||||
|
||||
import com.google.gson.Gson
|
||||
import com.google.gson.TypeAdapter
|
||||
import com.google.gson.annotations.JsonAdapter
|
||||
import com.google.gson.stream.JsonReader
|
||||
import com.google.gson.stream.JsonWriter
|
||||
import ru.dbotthepony.kommons.io.StreamCodec
|
||||
import ru.dbotthepony.kommons.io.readUUID
|
||||
import ru.dbotthepony.kommons.io.readVector2d
|
||||
@ -18,6 +23,7 @@ import ru.dbotthepony.kstarbound.server.world.ServerWorld
|
||||
import java.io.DataInputStream
|
||||
import java.io.DataOutputStream
|
||||
import java.util.UUID
|
||||
import kotlin.math.roundToInt
|
||||
|
||||
// original game has MVariant here
|
||||
// MVariant prepends InvalidValue to Variant<> template
|
||||
@ -26,7 +32,7 @@ import java.util.UUID
|
||||
// typedef MVariant<WarpToWorld, WarpToPlayer, WarpAlias> WarpAction;
|
||||
// -> Variant<InvalidType, WarpToWorld, WarpToPlayer, WarpAlias> WarpAction
|
||||
// hence WarpToWorld has index 1, WarpToPlayer 2, WarpAlias 3
|
||||
|
||||
@JsonAdapter(SpawnTarget.Adapter::class)
|
||||
sealed class SpawnTarget {
|
||||
abstract fun write(stream: DataOutputStream, isLegacy: Boolean)
|
||||
abstract fun resolve(world: ServerWorld): Vector2d?
|
||||
@ -41,7 +47,7 @@ sealed class SpawnTarget {
|
||||
}
|
||||
|
||||
override fun toString(): String {
|
||||
return "SpawnTarget.SpawnTarget"
|
||||
return "Whatever"
|
||||
}
|
||||
}
|
||||
|
||||
@ -56,7 +62,7 @@ sealed class SpawnTarget {
|
||||
}
|
||||
|
||||
override fun toString(): String {
|
||||
return "SpawnTarget.Entity[$id]"
|
||||
return id
|
||||
}
|
||||
}
|
||||
|
||||
@ -72,7 +78,7 @@ sealed class SpawnTarget {
|
||||
}
|
||||
|
||||
override fun toString(): String {
|
||||
return "SpawnTarget.Position[$position]"
|
||||
return "${position.x.roundToInt()}.${position.y.roundToInt()}"
|
||||
}
|
||||
|
||||
override fun resolve(world: ServerWorld): Vector2d {
|
||||
@ -92,7 +98,7 @@ sealed class SpawnTarget {
|
||||
}
|
||||
|
||||
override fun toString(): String {
|
||||
return "SpawnTarget.X[$position]"
|
||||
return position.roundToInt().toString()
|
||||
}
|
||||
|
||||
override fun resolve(world: ServerWorld): Vector2d {
|
||||
@ -100,7 +106,20 @@ sealed class SpawnTarget {
|
||||
}
|
||||
}
|
||||
|
||||
class Adapter : TypeAdapter<SpawnTarget>() {
|
||||
override fun write(out: JsonWriter, value: SpawnTarget) {
|
||||
out.value(value.toString())
|
||||
}
|
||||
|
||||
override fun read(`in`: JsonReader): SpawnTarget {
|
||||
return parse(`in`.nextString())
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
private val position = Regex("\\d+.\\d+")
|
||||
private val positionX = Regex("\\d+")
|
||||
|
||||
fun read(stream: DataInputStream, isLegacy: Boolean): SpawnTarget {
|
||||
return when (val type = stream.readUnsignedByte()) {
|
||||
0 -> Whatever
|
||||
@ -110,14 +129,31 @@ sealed class SpawnTarget {
|
||||
else -> throw IllegalArgumentException("Unknown SpawnTarget type $type!")
|
||||
}
|
||||
}
|
||||
|
||||
fun parse(value: String): SpawnTarget {
|
||||
val matchPos = position.matchEntire(value)
|
||||
|
||||
if (matchPos != null) {
|
||||
return Position(Vector2d(matchPos.groups[0]!!.value.toDouble(), matchPos.groups[0]!!.value.toDouble()))
|
||||
}
|
||||
|
||||
val matchX = positionX.matchEntire(value)
|
||||
|
||||
if (matchX != null) {
|
||||
return X(matchX.groups[0]!!.value.toDouble())
|
||||
}
|
||||
|
||||
return Entity(value)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@JsonAdapter(WarpAction.Adapter::class)
|
||||
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() {
|
||||
data class World(val worldID: WorldID, val target: SpawnTarget = SpawnTarget.Whatever) : WarpAction() {
|
||||
override fun write(stream: DataOutputStream, isLegacy: Boolean) {
|
||||
stream.writeByte(1)
|
||||
worldID.write(stream, isLegacy)
|
||||
@ -125,7 +161,14 @@ sealed class WarpAction {
|
||||
}
|
||||
|
||||
override fun resolve(connection: ServerConnection): WorldID {
|
||||
TODO("Not yet implemented")
|
||||
return worldID
|
||||
}
|
||||
|
||||
override fun toString(): String {
|
||||
if (target != SpawnTarget.Whatever)
|
||||
return "$worldID=$target"
|
||||
|
||||
return "$worldID"
|
||||
}
|
||||
}
|
||||
|
||||
@ -141,6 +184,20 @@ sealed class WarpAction {
|
||||
|
||||
return connection.server.clientByUUID(uuid)?.world?.worldID ?: WorldID.Limbo
|
||||
}
|
||||
|
||||
override fun toString(): String {
|
||||
return "Player:$uuid"
|
||||
}
|
||||
}
|
||||
|
||||
class Adapter(gson: Gson) : TypeAdapter<WarpAction>() {
|
||||
override fun write(out: JsonWriter, value: WarpAction) {
|
||||
out.value(value.toString())
|
||||
}
|
||||
|
||||
override fun read(`in`: JsonReader): WarpAction {
|
||||
return parse(`in`.nextString())
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
@ -161,45 +218,71 @@ sealed class WarpAction {
|
||||
}
|
||||
}
|
||||
|
||||
fun parse(value: String): WarpAction {
|
||||
if (value.lowercase() == "return") {
|
||||
return WarpAlias.Return
|
||||
} else if (value.lowercase() == "orbitedworld") {
|
||||
return WarpAlias.OrbitedWorld
|
||||
} else if (value.lowercase().startsWith("player:")) {
|
||||
return Player(UUID.fromString(value.substring(7)))
|
||||
} else {
|
||||
val parts = value.split('=')
|
||||
val world = WorldID.parse(parts[0])
|
||||
var spawnTarget: SpawnTarget = SpawnTarget.Whatever
|
||||
|
||||
if (parts.size == 2) {
|
||||
spawnTarget = SpawnTarget.parse(parts[1])
|
||||
}
|
||||
|
||||
return World(world, spawnTarget)
|
||||
}
|
||||
}
|
||||
|
||||
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) {
|
||||
final 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)
|
||||
}
|
||||
|
||||
abstract fun remap(connection: ServerConnection): WarpAction
|
||||
|
||||
final override fun resolve(connection: ServerConnection): WorldID {
|
||||
throw RuntimeException("Trying to use WarpAlias as regular warp action")
|
||||
}
|
||||
|
||||
object Return : WarpAlias(0) {
|
||||
override fun resolve(connection: ServerConnection): WorldID {
|
||||
TODO("Not yet implemented")
|
||||
override fun remap(connection: ServerConnection): WarpAction {
|
||||
return connection.returnWarp ?: World(connection.shipWorld.worldID)
|
||||
}
|
||||
|
||||
override fun toString(): String {
|
||||
return "WarpAlias.Return"
|
||||
return "Return"
|
||||
}
|
||||
}
|
||||
|
||||
object OrbitedWorld : WarpAlias(1) {
|
||||
override fun resolve(connection: ServerConnection): WorldID {
|
||||
TODO("Not yet implemented")
|
||||
override fun remap(connection: ServerConnection): WarpAction {
|
||||
return connection.orbitalWarpAction.orNull()?.first ?: World(connection.shipWorld.worldID)
|
||||
}
|
||||
|
||||
override fun toString(): String {
|
||||
return "WarpAlias.OrbitedWorld"
|
||||
return "OrbitedWorld"
|
||||
}
|
||||
}
|
||||
|
||||
object OwnShip : WarpAlias(2) {
|
||||
override fun resolve(connection: ServerConnection): WorldID {
|
||||
return connection.shipWorld.worldID
|
||||
override fun remap(connection: ServerConnection): WarpAction {
|
||||
return World(connection.shipWorld.worldID)
|
||||
}
|
||||
|
||||
override fun toString(): String {
|
||||
return "WarpAlias.OwnShip"
|
||||
return "OwnShip"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,10 +1,51 @@
|
||||
package ru.dbotthepony.kstarbound.defs
|
||||
|
||||
import com.google.common.collect.ImmutableList
|
||||
import ru.dbotthepony.kstarbound.defs.world.FloatingDungeonWorldParameters
|
||||
import ru.dbotthepony.kstarbound.defs.world.TerrestrialWorldParameters
|
||||
import ru.dbotthepony.kstarbound.defs.world.VisitableWorldParameters
|
||||
import ru.dbotthepony.kstarbound.json.builder.JsonFactory
|
||||
import java.util.function.Predicate
|
||||
|
||||
@JsonFactory
|
||||
data class UniverseServerConfig(
|
||||
// in milliseconds
|
||||
val clockUpdatePacketInterval: Long = 500L,
|
||||
)
|
||||
val findStarterWorldParameters: StarterWorld,
|
||||
val queuedFlightWaitTime: Double = 0.0,
|
||||
) {
|
||||
@JsonFactory
|
||||
data class WorldPredicate(
|
||||
val terrestrialBiome: String? = null,
|
||||
val terrestrialSize: String? = null,
|
||||
val floatingDungeon: String? = null,
|
||||
) : Predicate<VisitableWorldParameters> {
|
||||
override fun test(t: VisitableWorldParameters): Boolean {
|
||||
if (terrestrialBiome != null) {
|
||||
if (t !is TerrestrialWorldParameters) return false
|
||||
if (t.primaryBiome != terrestrialBiome) return false
|
||||
}
|
||||
|
||||
if (terrestrialSize != null) {
|
||||
if (t !is TerrestrialWorldParameters) return false
|
||||
if (t.sizeName != terrestrialSize) return false
|
||||
}
|
||||
|
||||
if (floatingDungeon != null) {
|
||||
if (t !is FloatingDungeonWorldParameters) return false
|
||||
if (t.primaryDungeon != floatingDungeon) return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
@JsonFactory
|
||||
data class StarterWorld(
|
||||
val tries: Int,
|
||||
val range: Int,
|
||||
val starterWorld: WorldPredicate,
|
||||
val requiredSystemWorlds: ImmutableList<WorldPredicate> = ImmutableList.of(),
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -1,16 +1,24 @@
|
||||
package ru.dbotthepony.kstarbound.defs
|
||||
|
||||
import com.google.gson.Gson
|
||||
import com.google.gson.TypeAdapter
|
||||
import com.google.gson.annotations.JsonAdapter
|
||||
import com.google.gson.stream.JsonReader
|
||||
import com.google.gson.stream.JsonWriter
|
||||
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.util.toStarboundString
|
||||
import ru.dbotthepony.kstarbound.util.uuidFromStarboundString
|
||||
import ru.dbotthepony.kstarbound.world.UniversePos
|
||||
import java.io.DataInputStream
|
||||
import java.io.DataOutputStream
|
||||
import java.util.UUID
|
||||
|
||||
@JsonAdapter(WorldID.Adapter::class)
|
||||
sealed class WorldID {
|
||||
abstract fun write(stream: DataOutputStream, isLegacy: Boolean)
|
||||
val isLimbo: Boolean get() = this is Limbo
|
||||
@ -21,7 +29,7 @@ sealed class WorldID {
|
||||
}
|
||||
|
||||
override fun toString(): String {
|
||||
return "WorldID.Limbo"
|
||||
return "Nowhere"
|
||||
}
|
||||
}
|
||||
|
||||
@ -32,7 +40,7 @@ sealed class WorldID {
|
||||
}
|
||||
|
||||
override fun toString(): String {
|
||||
return "WorldID.Celestial[$pos]"
|
||||
return "CelestialWorld:$pos"
|
||||
}
|
||||
}
|
||||
|
||||
@ -43,7 +51,7 @@ sealed class WorldID {
|
||||
}
|
||||
|
||||
override fun toString(): String {
|
||||
return "WorldID.ShipWorld[${uuid.toString().substring(0, 8)}]"
|
||||
return "ClientShipWorld:${uuid.toStarboundString()}"
|
||||
}
|
||||
}
|
||||
|
||||
@ -66,7 +74,17 @@ sealed class WorldID {
|
||||
}
|
||||
|
||||
override fun toString(): String {
|
||||
return "WorldID.Instance[$name, uuid=$uuid, threat level=$threatLevel]"
|
||||
return "InstanceWorld:$name:${uuid?.toStarboundString() ?: "-"}:${threatLevel ?: "-"}"
|
||||
}
|
||||
}
|
||||
|
||||
class Adapter(gson: Gson) : TypeAdapter<WorldID>() {
|
||||
override fun write(out: JsonWriter, value: WorldID) {
|
||||
out.value(value.toString())
|
||||
}
|
||||
|
||||
override fun read(`in`: JsonReader): WorldID {
|
||||
return parse(`in`.nextString())
|
||||
}
|
||||
}
|
||||
|
||||
@ -74,6 +92,45 @@ sealed class WorldID {
|
||||
val CODEC = nativeCodec(::read, WorldID::write)
|
||||
val LEGACY_CODEC = legacyCodec(::read, WorldID::write)
|
||||
|
||||
fun parse(value: String): WorldID {
|
||||
if (value.isBlank())
|
||||
return Limbo
|
||||
|
||||
val parts = value.split(':')
|
||||
|
||||
return when (val type = parts[0].lowercase()) {
|
||||
"nowhere" -> Limbo
|
||||
"instanceworld" -> {
|
||||
val rest = parts[1].split(':')
|
||||
|
||||
if (rest.isEmpty() || rest.size > 3) {
|
||||
throw IllegalArgumentException("Malformed InstanceWorld string: $value")
|
||||
}
|
||||
|
||||
val name = rest[0]
|
||||
var uuid: UUID? = null
|
||||
var threatLevel: Double? = null
|
||||
|
||||
if (rest.size > 1) {
|
||||
uuid = if (rest[1] == "-") null else uuidFromStarboundString(rest[1])
|
||||
|
||||
if (rest.size > 2) {
|
||||
threatLevel = if (rest[2] == "-") null else rest[2].toDouble()
|
||||
|
||||
if (threatLevel != null && threatLevel < 0.0)
|
||||
throw IllegalArgumentException("InstanceWorld threat level is negative: $value")
|
||||
}
|
||||
}
|
||||
|
||||
Instance(name, uuid, threatLevel)
|
||||
}
|
||||
|
||||
"celestialworld" -> Celestial(UniversePos.parse(parts[1]))
|
||||
"clientshipworld" -> ShipWorld(uuidFromStarboundString(parts[1]))
|
||||
else -> throw IllegalArgumentException("Invalid WorldID type: $type (input: $value)")
|
||||
}
|
||||
}
|
||||
|
||||
fun read(stream: DataInputStream, isLegacy: Boolean): WorldID {
|
||||
return when (val type = stream.readUnsignedByte()) {
|
||||
0 -> Limbo
|
||||
|
@ -1,12 +1,14 @@
|
||||
package ru.dbotthepony.kstarbound.defs.actor.player
|
||||
|
||||
import com.google.common.collect.ImmutableSet
|
||||
import it.unimi.dsi.fastutil.objects.ObjectArraySet
|
||||
import ru.dbotthepony.kommons.guava.immutableSet
|
||||
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.readDouble
|
||||
import ru.dbotthepony.kstarbound.io.readInternedString
|
||||
import ru.dbotthepony.kstarbound.io.writeDouble
|
||||
import ru.dbotthepony.kstarbound.json.builder.JsonFactory
|
||||
import ru.dbotthepony.kstarbound.network.syncher.legacyCodec
|
||||
import ru.dbotthepony.kstarbound.network.syncher.nativeCodec
|
||||
@ -19,15 +21,15 @@ data class ShipUpgrades(
|
||||
val maxFuel: Int = 0,
|
||||
val crewSize: Int = 0,
|
||||
val fuelEfficiency: Double = 1.0,
|
||||
val shipSpeed: Int = 0,
|
||||
val shipSpeed: Double = 30.0,
|
||||
val capabilities: ImmutableSet<String> = ImmutableSet.of()
|
||||
) {
|
||||
constructor(stream: DataInputStream, isLegacy: Boolean) : this(
|
||||
stream.readInt(),
|
||||
stream.readInt(),
|
||||
stream.readInt(),
|
||||
if (isLegacy) stream.readFloat().toDouble() else stream.readDouble(),
|
||||
stream.readInt(),
|
||||
stream.readDouble(isLegacy),
|
||||
stream.readDouble(isLegacy),
|
||||
ImmutableSet.copyOf(stream.readCollection { readInternedString() })
|
||||
)
|
||||
|
||||
@ -42,17 +44,24 @@ data class ShipUpgrades(
|
||||
)
|
||||
}
|
||||
|
||||
fun addCapability(capability: String): ShipUpgrades {
|
||||
val copy = ObjectArraySet(capabilities)
|
||||
copy.add(capability)
|
||||
return copy(capabilities = ImmutableSet.copyOf(copy))
|
||||
}
|
||||
|
||||
fun removeCapability(capability: String): ShipUpgrades {
|
||||
val copy = ObjectArraySet(capabilities)
|
||||
copy.remove(capability)
|
||||
return copy(capabilities = ImmutableSet.copyOf(copy))
|
||||
}
|
||||
|
||||
fun write(stream: DataOutputStream, isLegacy: Boolean) {
|
||||
stream.writeInt(shipLevel)
|
||||
stream.writeInt(maxFuel)
|
||||
stream.writeInt(crewSize)
|
||||
|
||||
if (isLegacy)
|
||||
stream.writeFloat(fuelEfficiency.toFloat())
|
||||
else
|
||||
stream.writeDouble(fuelEfficiency)
|
||||
|
||||
stream.writeInt(shipSpeed)
|
||||
stream.writeDouble(fuelEfficiency, isLegacy)
|
||||
stream.writeDouble(shipSpeed, isLegacy)
|
||||
stream.writeCollection(capabilities) { writeBinaryString(it) }
|
||||
}
|
||||
|
||||
|
@ -2,6 +2,7 @@ package ru.dbotthepony.kstarbound.defs.world
|
||||
|
||||
import com.google.gson.JsonObject
|
||||
import ru.dbotthepony.kommons.gson.set
|
||||
import ru.dbotthepony.kommons.io.writeBinaryString
|
||||
import ru.dbotthepony.kommons.math.RGBAColor
|
||||
import ru.dbotthepony.kommons.util.AABBi
|
||||
import ru.dbotthepony.kommons.vector.Vector2d
|
||||
@ -9,9 +10,14 @@ import ru.dbotthepony.kommons.vector.Vector2i
|
||||
import ru.dbotthepony.kstarbound.GlobalDefaults
|
||||
import ru.dbotthepony.kstarbound.Starbound
|
||||
import ru.dbotthepony.kstarbound.fromJson
|
||||
import ru.dbotthepony.kstarbound.io.readColor
|
||||
import ru.dbotthepony.kstarbound.io.readInternedString
|
||||
import ru.dbotthepony.kstarbound.io.writeColor
|
||||
import ru.dbotthepony.kstarbound.json.builder.JsonFactory
|
||||
import ru.dbotthepony.kstarbound.util.random.nextRange
|
||||
import ru.dbotthepony.kstarbound.util.random.random
|
||||
import java.io.DataInputStream
|
||||
import java.io.DataOutputStream
|
||||
import kotlin.properties.Delegates
|
||||
|
||||
class AsteroidsWorldParameters : VisitableWorldParameters() {
|
||||
@ -68,6 +74,26 @@ class AsteroidsWorldParameters : VisitableWorldParameters() {
|
||||
data[k] = v
|
||||
}
|
||||
|
||||
override fun read0(stream: DataInputStream) {
|
||||
super.read0(stream)
|
||||
|
||||
asteroidTopLevel = stream.readInt()
|
||||
asteroidBottomLevel = stream.readInt()
|
||||
blendSize = stream.readFloat().toDouble()
|
||||
asteroidBiome = stream.readInternedString()
|
||||
ambientLightLevel = stream.readColor()
|
||||
}
|
||||
|
||||
override fun write0(stream: DataOutputStream) {
|
||||
super.write0(stream)
|
||||
|
||||
stream.writeInt(asteroidTopLevel)
|
||||
stream.writeInt(asteroidBottomLevel)
|
||||
stream.writeFloat(blendSize.toFloat())
|
||||
stream.writeBinaryString(asteroidBiome)
|
||||
stream.writeColor(ambientLightLevel)
|
||||
}
|
||||
|
||||
override fun createLayout(seed: Long): WorldLayout {
|
||||
val random = random(seed)
|
||||
val terrain = GlobalDefaults.asteroidWorlds.terrains.random(random)
|
||||
|
@ -10,13 +10,23 @@ import com.google.gson.TypeAdapterFactory
|
||||
import com.google.gson.reflect.TypeToken
|
||||
import com.google.gson.stream.JsonReader
|
||||
import com.google.gson.stream.JsonWriter
|
||||
import it.unimi.dsi.fastutil.io.FastByteArrayInputStream
|
||||
import it.unimi.dsi.fastutil.io.FastByteArrayOutputStream
|
||||
import ru.dbotthepony.kommons.gson.consumeNull
|
||||
import ru.dbotthepony.kommons.gson.set
|
||||
import ru.dbotthepony.kommons.gson.value
|
||||
import ru.dbotthepony.kommons.io.readByteArray
|
||||
import ru.dbotthepony.kommons.io.writeBinaryString
|
||||
import ru.dbotthepony.kommons.io.writeByteArray
|
||||
import ru.dbotthepony.kstarbound.Starbound
|
||||
import ru.dbotthepony.kstarbound.io.readInternedString
|
||||
import ru.dbotthepony.kstarbound.json.builder.JsonFactory
|
||||
import ru.dbotthepony.kstarbound.json.readJsonElement
|
||||
import ru.dbotthepony.kstarbound.json.writeJsonElement
|
||||
import ru.dbotthepony.kstarbound.util.random.random
|
||||
import ru.dbotthepony.kstarbound.world.UniversePos
|
||||
import java.io.DataInputStream
|
||||
import java.io.DataOutputStream
|
||||
|
||||
class CelestialParameters private constructor(val coordinate: UniversePos, val seed: Long, val name: String, val parameters: JsonObject, marker: Nothing?) {
|
||||
constructor(coordinate: UniversePos, seed: Long, name: String, parameters: JsonObject) : this(coordinate, seed, name, parameters, null) {
|
||||
@ -55,6 +65,40 @@ class CelestialParameters private constructor(val coordinate: UniversePos, val s
|
||||
this.visitableParameters = visitableParameters
|
||||
}
|
||||
|
||||
private constructor(stream: DataInputStream, isLegacy: Boolean) : this(
|
||||
UniversePos(stream, isLegacy),
|
||||
stream.readLong(),
|
||||
stream.readInternedString(),
|
||||
stream.readJsonElement() as JsonObject,
|
||||
VisitableWorldParameters.fromNetwork(stream, isLegacy)
|
||||
)
|
||||
|
||||
private fun write0(stream: DataOutputStream, isLegacy: Boolean) {
|
||||
coordinate.write(stream, isLegacy)
|
||||
stream.writeLong(seed)
|
||||
stream.writeBinaryString(name)
|
||||
stream.writeJsonElement(parameters)
|
||||
|
||||
val visitableParameters = visitableParameters
|
||||
|
||||
if (visitableParameters == null) {
|
||||
stream.writeBoolean(false)
|
||||
} else {
|
||||
visitableParameters.write(stream, isLegacy)
|
||||
}
|
||||
}
|
||||
|
||||
fun write(stream: DataOutputStream, isLegacy: Boolean) {
|
||||
if (isLegacy) {
|
||||
// holy fucking shit
|
||||
val wrap = FastByteArrayOutputStream()
|
||||
write0(DataOutputStream(wrap), true)
|
||||
stream.writeByteArray(wrap.array, 0, wrap.length)
|
||||
} else {
|
||||
write0(stream, false)
|
||||
}
|
||||
}
|
||||
|
||||
var visitableParameters: VisitableWorldParameters? = null
|
||||
private set
|
||||
|
||||
@ -80,5 +124,16 @@ class CelestialParameters private constructor(val coordinate: UniversePos, val s
|
||||
return CelestialParameters(read.coordinate, read.seed, read.name, read.parameters, read.visitableParameters)
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
fun read(stream: DataInputStream, isLegacy: Boolean): CelestialParameters {
|
||||
if (isLegacy) {
|
||||
val wrap = FastByteArrayInputStream(stream.readByteArray())
|
||||
return CelestialParameters(DataInputStream(wrap), true)
|
||||
} else {
|
||||
return CelestialParameters(stream, false)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,7 +0,0 @@
|
||||
package ru.dbotthepony.kstarbound.defs.world
|
||||
|
||||
import it.unimi.dsi.fastutil.ints.Int2ObjectMap
|
||||
import ru.dbotthepony.kstarbound.json.builder.JsonFactory
|
||||
|
||||
@JsonFactory
|
||||
data class CelestialPlanet(val parameters: CelestialParameters, val satellites: Int2ObjectMap<CelestialParameters>)
|
@ -1,10 +1,23 @@
|
||||
package ru.dbotthepony.kstarbound.defs.world
|
||||
|
||||
import com.google.gson.JsonObject
|
||||
import ru.dbotthepony.kommons.gson.set
|
||||
import ru.dbotthepony.kommons.io.writeBinaryString
|
||||
import ru.dbotthepony.kommons.math.RGBAColor
|
||||
import ru.dbotthepony.kommons.vector.Vector2d
|
||||
import ru.dbotthepony.kstarbound.GlobalDefaults
|
||||
import ru.dbotthepony.kstarbound.Registries
|
||||
import ru.dbotthepony.kstarbound.Starbound
|
||||
import ru.dbotthepony.kstarbound.fromJson
|
||||
import ru.dbotthepony.kstarbound.io.readColor
|
||||
import ru.dbotthepony.kstarbound.io.readInternedString
|
||||
import ru.dbotthepony.kstarbound.io.readNullableString
|
||||
import ru.dbotthepony.kstarbound.io.writeColor
|
||||
import ru.dbotthepony.kstarbound.io.writeNullableString
|
||||
import ru.dbotthepony.kstarbound.json.builder.JsonFactory
|
||||
import ru.dbotthepony.kstarbound.util.random.random
|
||||
import java.io.DataInputStream
|
||||
import java.io.DataOutputStream
|
||||
import kotlin.properties.Delegates
|
||||
|
||||
class FloatingDungeonWorldParameters : VisitableWorldParameters() {
|
||||
@ -58,6 +71,77 @@ class FloatingDungeonWorldParameters : VisitableWorldParameters() {
|
||||
return layout
|
||||
}
|
||||
|
||||
@JsonFactory
|
||||
data class JsonData(
|
||||
val dungeonSurfaceHeight: Int,
|
||||
val dungeonUndergroundLevel: Int,
|
||||
val primaryDungeon: String,
|
||||
val biome: String? = null,
|
||||
val ambientLightLevel: RGBAColor,
|
||||
val dayMusicTrack: String? = null,
|
||||
val nightMusicTrack: String? = null,
|
||||
val dayAmbientNoises: String? = null,
|
||||
val nightAmbientNoises: String? = null,
|
||||
)
|
||||
|
||||
override fun fromJson(data: JsonObject) {
|
||||
super.fromJson(data)
|
||||
|
||||
val read = Starbound.gson.fromJson(data, JsonData::class.java)
|
||||
|
||||
dungeonSurfaceHeight = read.dungeonSurfaceHeight
|
||||
dungeonUndergroundLevel = read.dungeonUndergroundLevel
|
||||
primaryDungeon = read.primaryDungeon
|
||||
biome = read.biome
|
||||
ambientLightLevel = read.ambientLightLevel
|
||||
dayMusicTrack = read.dayMusicTrack
|
||||
nightMusicTrack = read.nightMusicTrack
|
||||
dayAmbientNoises = read.dayAmbientNoises
|
||||
nightAmbientNoises = read.nightAmbientNoises
|
||||
}
|
||||
|
||||
override fun toJson(data: JsonObject, isLegacy: Boolean) {
|
||||
super.toJson(data, isLegacy)
|
||||
|
||||
val serialize = Starbound.gson.toJsonTree(JsonData(
|
||||
dungeonSurfaceHeight, dungeonUndergroundLevel, primaryDungeon, biome, ambientLightLevel, dayMusicTrack, nightMusicTrack, dayAmbientNoises, nightAmbientNoises
|
||||
)) as JsonObject
|
||||
|
||||
for ((k, v) in serialize.entrySet()) {
|
||||
data[k] = v
|
||||
}
|
||||
}
|
||||
|
||||
override fun read0(stream: DataInputStream) {
|
||||
super.read0(stream)
|
||||
|
||||
dungeonBaseHeight = stream.readInt()
|
||||
dungeonSurfaceHeight = stream.readInt()
|
||||
dungeonUndergroundLevel = stream.readInt()
|
||||
primaryDungeon = stream.readInternedString()
|
||||
biome = stream.readNullableString()
|
||||
ambientLightLevel = stream.readColor()
|
||||
dayMusicTrack = stream.readNullableString()
|
||||
nightMusicTrack = stream.readNullableString()
|
||||
dayAmbientNoises = stream.readNullableString()
|
||||
nightAmbientNoises = stream.readNullableString()
|
||||
}
|
||||
|
||||
override fun write0(stream: DataOutputStream) {
|
||||
super.write0(stream)
|
||||
|
||||
stream.writeInt(dungeonBaseHeight)
|
||||
stream.writeInt(dungeonSurfaceHeight)
|
||||
stream.writeInt(dungeonUndergroundLevel)
|
||||
stream.writeBinaryString(primaryDungeon)
|
||||
stream.writeNullableString(biome)
|
||||
stream.writeColor(ambientLightLevel)
|
||||
stream.writeNullableString(dayMusicTrack)
|
||||
stream.writeNullableString(nightMusicTrack)
|
||||
stream.writeNullableString(dayAmbientNoises)
|
||||
stream.writeNullableString(nightAmbientNoises)
|
||||
}
|
||||
|
||||
companion object {
|
||||
fun generate(typeName: String): FloatingDungeonWorldParameters {
|
||||
val config = GlobalDefaults.dungeonWorlds[typeName] ?: throw NoSuchElementException("Unknown dungeon world type $typeName!")
|
||||
|
@ -123,15 +123,26 @@ data class SkyWorldHorizon(val center: Vector2d, val scale: Double, val rotation
|
||||
|
||||
@JsonFactory
|
||||
data class SkyParameters(
|
||||
var skyType: SkyType = SkyType.BARREN,
|
||||
var seed: Long = 0L,
|
||||
var dayLength: Double? = null,
|
||||
var horizonClouds: Boolean = false,
|
||||
var skyColoring: Either<SkyColoring, RGBAColor> = Either.left(SkyColoring()),
|
||||
var spaceLevel: Double? = null,
|
||||
var surfaceLevel: Double? = null,
|
||||
var nearbyPlanet: Pair<List<Pair<String, Double>>, Vector2d>? = null,
|
||||
val skyType: SkyType = SkyType.BARREN,
|
||||
val seed: Long = 0L,
|
||||
val dayLength: Double? = null,
|
||||
val horizonClouds: Boolean = false,
|
||||
val skyColoring: Either<SkyColoring, RGBAColor> = Either.left(SkyColoring()),
|
||||
val spaceLevel: Double? = null,
|
||||
val surfaceLevel: Double? = null,
|
||||
val nearbyPlanet: Planet? = null,
|
||||
val nearbyMoons: ImmutableList<Planet> = ImmutableList.of(),
|
||||
val horizonImages: ImmutableList<HorizonImage> = ImmutableList.of(),
|
||||
) {
|
||||
@JsonFactory
|
||||
data class HorizonImage(val left: String, val right: String)
|
||||
|
||||
@JsonFactory
|
||||
data class Layer(val image: String, val scale: Double)
|
||||
|
||||
@JsonFactory
|
||||
data class Planet(val pos: Vector2d, val layers: ImmutableList<Layer>)
|
||||
|
||||
companion object {
|
||||
suspend fun create(coordinate: UniversePos, universe: Universe): SkyParameters {
|
||||
if (coordinate.isSystem)
|
||||
|
@ -0,0 +1,41 @@
|
||||
package ru.dbotthepony.kstarbound.defs.world
|
||||
|
||||
import com.google.common.collect.ImmutableList
|
||||
import com.google.common.collect.ImmutableMap
|
||||
import ru.dbotthepony.kommons.vector.Vector2d
|
||||
import ru.dbotthepony.kstarbound.collect.WeightedList
|
||||
import ru.dbotthepony.kstarbound.json.builder.JsonFactory
|
||||
|
||||
@JsonFactory
|
||||
data class SystemWorldConfig(
|
||||
val starGravitationalConstant: Double,
|
||||
val planetGravitationalConstant: Double,
|
||||
val emptyOrbitSize: Double,
|
||||
val unvisitablePlanetSize: Double,
|
||||
val floatingDungeonWorldSizes: ImmutableMap<String, Double>,
|
||||
val planetSizes: ImmutableList<Pair<Int, Double>>,
|
||||
val starSize: Double,
|
||||
val planetaryOrbitPadding: Vector2d,
|
||||
val satelliteOrbitPadding: Vector2d,
|
||||
val arrivalRange: Vector2d,
|
||||
val objectSpawnPadding: Double,
|
||||
val clientObjectSpawnPadding: Double,
|
||||
val objectSpawnInterval: Vector2d,
|
||||
val objectSpawnCycle: Double,
|
||||
val minObjectOrbitTime: Double,
|
||||
val asteroidBeamDistance: Double,
|
||||
val emptySkyParameters: SkyParameters,
|
||||
|
||||
val objectSpawnPool: WeightedList<String>,
|
||||
val initialObjectPools: WeightedList<Pair<Int, WeightedList<String>>>,
|
||||
|
||||
val clientShip: ClientShip,
|
||||
) {
|
||||
@JsonFactory
|
||||
data class ClientShip(
|
||||
val speed: Double,
|
||||
val orbitDistance: Double,
|
||||
val departTime: Double,
|
||||
val spaceDepartTime: Double,
|
||||
)
|
||||
}
|
@ -0,0 +1,62 @@
|
||||
package ru.dbotthepony.kstarbound.defs.world
|
||||
|
||||
import com.google.common.collect.ImmutableMap
|
||||
import com.google.gson.JsonObject
|
||||
import ru.dbotthepony.kommons.vector.Vector2d
|
||||
import ru.dbotthepony.kstarbound.defs.WarpAction
|
||||
import ru.dbotthepony.kstarbound.json.builder.JsonFactory
|
||||
import ru.dbotthepony.kstarbound.util.random.nextRange
|
||||
import ru.dbotthepony.kstarbound.util.random.random
|
||||
import ru.dbotthepony.kstarbound.util.random.staticRandom64
|
||||
import java.util.UUID
|
||||
|
||||
@JsonFactory
|
||||
data class SystemWorldObjectConfig(
|
||||
val warpAction: WarpAction,
|
||||
val orbitRange: Vector2d,
|
||||
val lifeTime: Vector2d,
|
||||
val permanent: Boolean = false,
|
||||
val moving: Boolean = false,
|
||||
val threatLevel: Double? = null,
|
||||
val skyParameters: SkyParameters,
|
||||
val parameters: JsonObject = JsonObject(),
|
||||
val speed: Double = 0.0,
|
||||
val generatedParameters: ImmutableMap<String, String> = ImmutableMap.of(),
|
||||
) {
|
||||
init {
|
||||
require(speed >= 0.0) { "Negative speed $speed" }
|
||||
}
|
||||
|
||||
fun create(uuid: UUID, name: String): Data {
|
||||
val random = random(staticRandom64(uuid.mostSignificantBits, uuid.leastSignificantBits))
|
||||
|
||||
return Data(
|
||||
warpAction = warpAction,
|
||||
orbitDistance = random.nextRange(orbitRange),
|
||||
lifeTime = random.nextRange(lifeTime),
|
||||
permanent = permanent,
|
||||
moving = moving,
|
||||
threatLevel = threatLevel,
|
||||
skyParameters = skyParameters,
|
||||
parameters = parameters,
|
||||
speed = speed,
|
||||
generatedParameters = generatedParameters,
|
||||
name = name,
|
||||
)
|
||||
}
|
||||
|
||||
@JsonFactory
|
||||
data class Data(
|
||||
val warpAction: WarpAction,
|
||||
val orbitDistance: Double,
|
||||
val lifeTime: Double,
|
||||
val permanent: Boolean = false,
|
||||
val moving: Boolean = false,
|
||||
val threatLevel: Double? = null,
|
||||
val skyParameters: SkyParameters,
|
||||
val parameters: JsonObject = JsonObject(),
|
||||
val speed: Double = 0.0,
|
||||
val generatedParameters: ImmutableMap<String, String> = ImmutableMap.of(),
|
||||
val name: String,
|
||||
)
|
||||
}
|
@ -13,6 +13,9 @@ import ru.dbotthepony.kommons.gson.get
|
||||
import ru.dbotthepony.kommons.gson.getArray
|
||||
import ru.dbotthepony.kommons.gson.getObject
|
||||
import ru.dbotthepony.kommons.gson.set
|
||||
import ru.dbotthepony.kommons.io.readCollection
|
||||
import ru.dbotthepony.kommons.io.writeBinaryString
|
||||
import ru.dbotthepony.kommons.io.writeCollection
|
||||
import ru.dbotthepony.kommons.util.Either
|
||||
import ru.dbotthepony.kommons.vector.Vector2d
|
||||
import ru.dbotthepony.kommons.vector.Vector2i
|
||||
@ -22,14 +25,22 @@ import ru.dbotthepony.kstarbound.Starbound
|
||||
import ru.dbotthepony.kstarbound.collect.WeightedList
|
||||
import ru.dbotthepony.kstarbound.defs.JsonDriven
|
||||
import ru.dbotthepony.kstarbound.defs.PerlinNoiseParameters
|
||||
import ru.dbotthepony.kstarbound.fromJson
|
||||
import ru.dbotthepony.kstarbound.io.readInternedString
|
||||
import ru.dbotthepony.kstarbound.io.readVector2d
|
||||
import ru.dbotthepony.kstarbound.io.writeStruct2d
|
||||
import ru.dbotthepony.kstarbound.json.builder.JsonFactory
|
||||
import ru.dbotthepony.kstarbound.json.mergeJson
|
||||
import ru.dbotthepony.kstarbound.json.pairAdapter
|
||||
import ru.dbotthepony.kstarbound.json.readJsonElement
|
||||
import ru.dbotthepony.kstarbound.json.stream
|
||||
import ru.dbotthepony.kstarbound.json.writeJsonElement
|
||||
import ru.dbotthepony.kstarbound.util.binnedChoice
|
||||
import ru.dbotthepony.kstarbound.util.random.AbstractPerlinNoise
|
||||
import ru.dbotthepony.kstarbound.util.random.nextRange
|
||||
import ru.dbotthepony.kstarbound.util.random.random
|
||||
import java.io.DataInputStream
|
||||
import java.io.DataOutputStream
|
||||
import java.util.random.RandomGenerator
|
||||
import kotlin.properties.Delegates
|
||||
|
||||
@ -71,7 +82,46 @@ class TerrestrialWorldParameters : VisitableWorldParameters() {
|
||||
|
||||
val encloseLiquids: Boolean,
|
||||
val fillMicrodungeons: Boolean,
|
||||
)
|
||||
) {
|
||||
constructor(stream: DataInputStream) : this(
|
||||
stream.readInternedString(),
|
||||
|
||||
stream.readInternedString(),
|
||||
stream.readInternedString(),
|
||||
stream.readInternedString(),
|
||||
stream.readInternedString(),
|
||||
stream.readInternedString(),
|
||||
stream.readInternedString(),
|
||||
|
||||
Either.left(stream.readUnsignedByte()),
|
||||
stream.readFloat().toDouble(),
|
||||
|
||||
Either.left(stream.readUnsignedByte()),
|
||||
stream.readInt(),
|
||||
|
||||
stream.readBoolean(),
|
||||
stream.readBoolean(),
|
||||
)
|
||||
|
||||
fun write(stream: DataOutputStream) {
|
||||
stream.writeBinaryString(biome)
|
||||
|
||||
stream.writeBinaryString(blockSelector)
|
||||
stream.writeBinaryString(fgCaveSelector)
|
||||
stream.writeBinaryString(bgCaveSelector)
|
||||
stream.writeBinaryString(fgOreSelector)
|
||||
stream.writeBinaryString(bgOreSelector)
|
||||
stream.writeBinaryString(subBlockSelector)
|
||||
|
||||
stream.writeByte(caveLiquid?.map({ it }, { Registries.liquid[it]?.id }) ?: 0)
|
||||
stream.writeFloat(caveLiquidSeedDensity.toFloat())
|
||||
stream.writeByte(oceanLiquid?.map({ it }, { Registries.liquid[it]?.id }) ?: 0)
|
||||
stream.writeInt(oceanLiquidLevel)
|
||||
|
||||
stream.writeBoolean(encloseLiquids)
|
||||
stream.writeBoolean(fillMicrodungeons)
|
||||
}
|
||||
}
|
||||
|
||||
@JsonFactory
|
||||
data class Layer(
|
||||
@ -89,7 +139,40 @@ class TerrestrialWorldParameters : VisitableWorldParameters() {
|
||||
|
||||
val secondaryRegionSizeRange: Vector2d,
|
||||
val subRegionSizeRange: Vector2d,
|
||||
)
|
||||
) {
|
||||
constructor(stream: DataInputStream) : this(
|
||||
stream.readInt(),
|
||||
stream.readInt(),
|
||||
ImmutableSet.copyOf(stream.readCollection { readInternedString() }),
|
||||
stream.readInt(),
|
||||
|
||||
Region(stream),
|
||||
Region(stream),
|
||||
|
||||
ImmutableList.copyOf(stream.readCollection { Region(this) }),
|
||||
ImmutableList.copyOf(stream.readCollection { Region(this) }),
|
||||
|
||||
stream.readVector2d(true),
|
||||
stream.readVector2d(true),
|
||||
)
|
||||
|
||||
fun write(stream: DataOutputStream) {
|
||||
stream.writeInt(layerMinHeight)
|
||||
stream.writeInt(layerBaseHeight)
|
||||
|
||||
stream.writeCollection(dungeons) { writeBinaryString(it) }
|
||||
stream.writeInt(dungeonXVariance)
|
||||
|
||||
primaryRegion.write(stream)
|
||||
primarySubRegion.write(stream)
|
||||
|
||||
stream.writeCollection(secondaryRegions) { it.write(this) }
|
||||
stream.writeCollection(secondarySubRegions) { it.write(this) }
|
||||
|
||||
stream.writeStruct2d(secondaryRegionSizeRange, true)
|
||||
stream.writeStruct2d(subRegionSizeRange, true)
|
||||
}
|
||||
}
|
||||
|
||||
override fun fromJson(data: JsonObject) {
|
||||
super.fromJson(data)
|
||||
@ -206,6 +289,52 @@ class TerrestrialWorldParameters : VisitableWorldParameters() {
|
||||
override val type: VisitableWorldParametersType
|
||||
get() = VisitableWorldParametersType.TERRESTRIAL
|
||||
|
||||
override fun read0(stream: DataInputStream) {
|
||||
super.read0(stream)
|
||||
|
||||
primaryBiome = stream.readInternedString()
|
||||
surfaceLiquid = Either.left(stream.readUnsignedByte())
|
||||
sizeName = stream.readInternedString()
|
||||
hueShift = stream.readFloat().toDouble()
|
||||
skyColoring = SkyColoring.read(stream, true)
|
||||
dayLength = stream.readFloat().toDouble()
|
||||
blendSize = stream.readFloat().toDouble()
|
||||
|
||||
blockNoiseConfig = Starbound.gson.fromJson(stream.readJsonElement())
|
||||
blendNoiseConfig = Starbound.gson.fromJson(stream.readJsonElement())
|
||||
|
||||
spaceLayer = Layer(stream)
|
||||
atmosphereLayer = Layer(stream)
|
||||
surfaceLayer = Layer(stream)
|
||||
subsurfaceLayer = Layer(stream)
|
||||
undergroundLayers = stream.readCollection { Layer(this) }
|
||||
coreLayer = Layer(stream)
|
||||
}
|
||||
|
||||
override fun write0(stream: DataOutputStream) {
|
||||
super.write0(stream)
|
||||
|
||||
stream.writeBinaryString(primaryBiome)
|
||||
stream.writeByte(surfaceLiquid?.map({ it }, { Registries.liquid[it]?.id }) ?: 0)
|
||||
stream.writeBinaryString(sizeName)
|
||||
stream.writeFloat(hueShift.toFloat())
|
||||
skyColoring.write(stream, true)
|
||||
stream.writeFloat(dayLength.toFloat())
|
||||
stream.writeFloat(blendSize.toFloat())
|
||||
|
||||
Starbound.legacyJson {
|
||||
stream.writeJsonElement(Starbound.gson.toJsonTree(blockNoiseConfig))
|
||||
stream.writeJsonElement(Starbound.gson.toJsonTree(blendNoiseConfig))
|
||||
}
|
||||
|
||||
spaceLayer.write(stream)
|
||||
atmosphereLayer.write(stream)
|
||||
surfaceLayer.write(stream)
|
||||
subsurfaceLayer.write(stream)
|
||||
stream.writeCollection(undergroundLayers) { it.write(this) }
|
||||
coreLayer.write(stream)
|
||||
}
|
||||
|
||||
// why
|
||||
override fun createLayout(seed: Long): WorldLayout {
|
||||
val layout = WorldLayout()
|
||||
|
@ -1,5 +1,7 @@
|
||||
package ru.dbotthepony.kstarbound.defs.world
|
||||
|
||||
import com.google.common.collect.ImmutableList
|
||||
import com.google.common.collect.ImmutableSet
|
||||
import com.google.gson.Gson
|
||||
import com.google.gson.JsonObject
|
||||
import com.google.gson.TypeAdapter
|
||||
@ -7,19 +9,41 @@ import com.google.gson.TypeAdapterFactory
|
||||
import com.google.gson.reflect.TypeToken
|
||||
import com.google.gson.stream.JsonReader
|
||||
import com.google.gson.stream.JsonWriter
|
||||
import it.unimi.dsi.fastutil.io.FastByteArrayInputStream
|
||||
import it.unimi.dsi.fastutil.io.FastByteArrayOutputStream
|
||||
import ru.dbotthepony.kommons.gson.consumeNull
|
||||
import ru.dbotthepony.kommons.gson.set
|
||||
import ru.dbotthepony.kommons.gson.value
|
||||
import ru.dbotthepony.kommons.io.readByteArray
|
||||
import ru.dbotthepony.kommons.io.readCollection
|
||||
import ru.dbotthepony.kommons.io.readVarInt
|
||||
import ru.dbotthepony.kommons.io.readVector2i
|
||||
import ru.dbotthepony.kommons.io.writeBinaryString
|
||||
import ru.dbotthepony.kommons.io.writeByteArray
|
||||
import ru.dbotthepony.kommons.io.writeCollection
|
||||
import ru.dbotthepony.kommons.io.writeStruct2i
|
||||
import ru.dbotthepony.kommons.util.Either
|
||||
import ru.dbotthepony.kommons.vector.Vector2d
|
||||
import ru.dbotthepony.kommons.vector.Vector2i
|
||||
import ru.dbotthepony.kstarbound.Starbound
|
||||
import ru.dbotthepony.kstarbound.collect.WeightedList
|
||||
import ru.dbotthepony.kstarbound.fromJson
|
||||
import ru.dbotthepony.kstarbound.io.readDouble
|
||||
import ru.dbotthepony.kstarbound.io.readInternedString
|
||||
import ru.dbotthepony.kstarbound.io.readNullable
|
||||
import ru.dbotthepony.kstarbound.io.writeDouble
|
||||
import ru.dbotthepony.kstarbound.io.writeNullable
|
||||
import ru.dbotthepony.kstarbound.json.builder.DispatchingAdapter
|
||||
import ru.dbotthepony.kstarbound.json.builder.IStringSerializable
|
||||
import ru.dbotthepony.kstarbound.json.builder.JsonFactory
|
||||
import ru.dbotthepony.kstarbound.json.readJsonElement
|
||||
import ru.dbotthepony.kstarbound.json.readJsonObject
|
||||
import ru.dbotthepony.kstarbound.json.writeJsonObject
|
||||
import java.io.DataInputStream
|
||||
import java.io.DataOutputStream
|
||||
import kotlin.properties.Delegates
|
||||
|
||||
// uint8_t
|
||||
enum class BeamUpRule(override val jsonName: String) : IStringSerializable {
|
||||
NOWHERE("Nowhere"),
|
||||
SURFACE("Surface"),
|
||||
@ -27,6 +51,7 @@ enum class BeamUpRule(override val jsonName: String) : IStringSerializable {
|
||||
ANYWHERE_WITH_WARNING("AnywhereWithWarning");
|
||||
}
|
||||
|
||||
// uint8_t
|
||||
enum class WorldEdgeForceRegion(override val jsonName: String) : IStringSerializable {
|
||||
NONE("None"),
|
||||
TOP("Top"),
|
||||
@ -34,6 +59,7 @@ enum class WorldEdgeForceRegion(override val jsonName: String) : IStringSerializ
|
||||
TOP_AND_BOTTOM("TopAndBottom");
|
||||
}
|
||||
|
||||
// uint8_t
|
||||
enum class VisitableWorldParametersType(override val jsonName: String, val token: TypeToken<out VisitableWorldParameters>) : IStringSerializable {
|
||||
TERRESTRIAL("TerrestrialWorldParameters", TypeToken.get(TerrestrialWorldParameters::class.java)),
|
||||
ASTEROIDS("AsteroidsWorldParameters", TypeToken.get(AsteroidsWorldParameters::class.java)),
|
||||
@ -169,4 +195,90 @@ abstract class VisitableWorldParameters {
|
||||
toJson(data, isLegacy)
|
||||
return data
|
||||
}
|
||||
|
||||
// called only for legacy protocol
|
||||
protected open fun read0(stream: DataInputStream) {
|
||||
typeName = stream.readInternedString()
|
||||
threatLevel = stream.readFloat().toDouble()
|
||||
worldSize = stream.readVector2i()
|
||||
gravity = Vector2d(y = stream.readFloat().toDouble())
|
||||
airless = stream.readBoolean()
|
||||
|
||||
val collection = stream.readCollection { readDouble() to readInternedString() }
|
||||
|
||||
if (collection.isNotEmpty())
|
||||
weatherPool = WeightedList(ImmutableList.copyOf(collection))
|
||||
|
||||
environmentStatusEffects = ImmutableSet.copyOf(stream.readCollection { readInternedString() })
|
||||
overrideTech = stream.readNullable { ImmutableSet.copyOf(readCollection { readInternedString() }) }
|
||||
globalDirectives = stream.readNullable { ImmutableSet.copyOf(readCollection { readInternedString() }) }
|
||||
|
||||
beamUpRule = BeamUpRule.entries[stream.readUnsignedByte()]
|
||||
disableDeathDrops = stream.readBoolean()
|
||||
terraformed = stream.readBoolean()
|
||||
worldEdgeForceRegions = WorldEdgeForceRegion.entries[stream.readUnsignedByte()]
|
||||
}
|
||||
|
||||
// called only for legacy protocol
|
||||
protected open fun write0(stream: DataOutputStream) {
|
||||
stream.writeBinaryString(typeName)
|
||||
stream.writeFloat(threatLevel.toFloat())
|
||||
stream.writeStruct2i(worldSize)
|
||||
stream.writeFloat(gravity.y.toFloat())
|
||||
stream.writeBoolean(airless)
|
||||
|
||||
if (weatherPool == null)
|
||||
stream.writeByte(0)
|
||||
else
|
||||
stream.writeCollection(weatherPool!!.parent) { writeDouble(it.first); writeBinaryString(it.second) }
|
||||
|
||||
stream.writeCollection(environmentStatusEffects) { writeBinaryString(it) }
|
||||
stream.writeNullable(overrideTech) { writeCollection(it) { writeBinaryString(it) } }
|
||||
stream.writeNullable(globalDirectives) { writeCollection(it) { writeBinaryString(it) } }
|
||||
stream.writeByte(beamUpRule.ordinal)
|
||||
stream.writeBoolean(disableDeathDrops)
|
||||
stream.writeBoolean(terraformed)
|
||||
stream.writeByte(worldEdgeForceRegions.ordinal)
|
||||
}
|
||||
|
||||
// because writing as json is too easy.
|
||||
// Tard.
|
||||
fun write(stream: DataOutputStream, isLegacy: Boolean) {
|
||||
if (isLegacy) {
|
||||
val wrapper = FastByteArrayOutputStream()
|
||||
wrapper.write(type.ordinal)
|
||||
write0(DataOutputStream(wrapper))
|
||||
stream.writeByteArray(wrapper.array, 0, wrapper.length)
|
||||
} else {
|
||||
stream.writeJsonObject(toJson())
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
fun fromNetwork(stream: DataInputStream, isLegacy: Boolean): VisitableWorldParameters? {
|
||||
if (isLegacy) {
|
||||
val readData = stream.readByteArray()
|
||||
|
||||
if (readData.isEmpty()) {
|
||||
return null
|
||||
}
|
||||
|
||||
val data = DataInputStream(FastByteArrayInputStream(readData))
|
||||
|
||||
val create = when (VisitableWorldParametersType.entries[data.readUnsignedByte()]) {
|
||||
VisitableWorldParametersType.TERRESTRIAL -> TerrestrialWorldParameters()
|
||||
VisitableWorldParametersType.ASTEROIDS -> AsteroidsWorldParameters()
|
||||
VisitableWorldParametersType.FLOATING_DUNGEON -> FloatingDungeonWorldParameters()
|
||||
}
|
||||
|
||||
create.read0(data)
|
||||
return create
|
||||
} else {
|
||||
if (!stream.readBoolean())
|
||||
return null
|
||||
|
||||
return Starbound.gson.fromJson(stream.readJsonObject(), VisitableWorldParameters::class.java)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -74,6 +74,29 @@ fun InputStream.readAABBLegacy(): AABB {
|
||||
return AABB(mins.toDoubleVector(), maxs.toDoubleVector())
|
||||
}
|
||||
|
||||
fun <S : InputStream, L, R> S.readEither(isLegacy: Boolean, left: S.() -> L, right: S.() -> R): Either<L, R> {
|
||||
var type = readUnsignedByte()
|
||||
|
||||
if (isLegacy)
|
||||
type--
|
||||
|
||||
return when (type) {
|
||||
0 -> Either.left(left(this))
|
||||
1 -> Either.right(right(this))
|
||||
else -> throw IllegalArgumentException("Unexpected either type $type")
|
||||
}
|
||||
}
|
||||
|
||||
fun <S : OutputStream, L, R> S.writeEither(value: Either<L, R>, isLegacy: Boolean, left: S.(L) -> Unit, right: S.(R) -> Unit) {
|
||||
if (value.isLeft) {
|
||||
if (isLegacy) write(1) else write(0)
|
||||
left(value.left())
|
||||
} else {
|
||||
if (isLegacy) write(2) else write(1)
|
||||
right(value.right())
|
||||
}
|
||||
}
|
||||
|
||||
fun InputStream.readAABBLegacyOptional(): KOptional<AABB> {
|
||||
val mins = readVector2f()
|
||||
val maxs = readVector2f()
|
||||
|
@ -50,7 +50,7 @@ abstract class Connection(val side: ConnectionSide, val type: ConnectionType) :
|
||||
var entityIDRange: IntRange by Delegates.notNull()
|
||||
private set
|
||||
|
||||
protected val coroutineScope = CoroutineScope(Starbound.COROUTINE_EXECUTOR)
|
||||
val scope = CoroutineScope(Starbound.COROUTINE_EXECUTOR)
|
||||
|
||||
var connectionID: Int = -1
|
||||
set(value) {
|
||||
@ -116,7 +116,7 @@ abstract class Connection(val side: ConnectionSide, val type: ConnectionType) :
|
||||
protected open fun onChannelClosed() {
|
||||
isConnected = false
|
||||
LOGGER.info("$this is terminated")
|
||||
coroutineScope.cancel("$this is terminated")
|
||||
scope.cancel("$this is terminated")
|
||||
}
|
||||
|
||||
fun bind(channel: Channel) {
|
||||
@ -248,6 +248,14 @@ abstract class Connection(val side: ConnectionSide, val type: ConnectionType) :
|
||||
private val warpActionCodec = StreamCodec.Pair(WarpAction.CODEC, WarpMode.CODEC).koptional()
|
||||
private val legacyWarpActionCodec = StreamCodec.Pair(WarpAction.LEGACY_CODEC, WarpMode.CODEC).koptional()
|
||||
|
||||
fun connectionForEntityID(id: Int): Int {
|
||||
if (id >= 0) {
|
||||
return 0
|
||||
} else {
|
||||
return (-id - 1) / 65536 + 1
|
||||
}
|
||||
}
|
||||
|
||||
val NIO_POOL by lazy {
|
||||
NioEventLoopGroup(1, ThreadFactoryBuilder().setDaemon(true).setNameFormat("Network IO %d").build())
|
||||
}
|
||||
|
@ -31,9 +31,11 @@ import ru.dbotthepony.kstarbound.network.packets.serverbound.HandshakeResponsePa
|
||||
import ru.dbotthepony.kstarbound.network.packets.ProtocolRequestPacket
|
||||
import ru.dbotthepony.kstarbound.network.packets.ProtocolResponsePacket
|
||||
import ru.dbotthepony.kstarbound.network.packets.StepUpdatePacket
|
||||
import ru.dbotthepony.kstarbound.network.packets.clientbound.CelestialResponsePacket
|
||||
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.EntityInteractResultPacket
|
||||
import ru.dbotthepony.kstarbound.network.packets.clientbound.EnvironmentUpdatePacket
|
||||
import ru.dbotthepony.kstarbound.network.packets.clientbound.FindUniqueEntityResponsePacket
|
||||
import ru.dbotthepony.kstarbound.network.packets.clientbound.LegacyTileArrayUpdatePacket
|
||||
@ -42,14 +44,24 @@ import ru.dbotthepony.kstarbound.network.packets.clientbound.PlayerWarpResultPac
|
||||
import ru.dbotthepony.kstarbound.network.packets.clientbound.ServerDisconnectPacket
|
||||
import ru.dbotthepony.kstarbound.network.packets.clientbound.ServerInfoPacket
|
||||
import ru.dbotthepony.kstarbound.network.packets.clientbound.SetPlayerStartPacket
|
||||
import ru.dbotthepony.kstarbound.network.packets.clientbound.SystemObjectCreatePacket
|
||||
import ru.dbotthepony.kstarbound.network.packets.clientbound.SystemObjectDestroyPacket
|
||||
import ru.dbotthepony.kstarbound.network.packets.clientbound.SystemShipCreatePacket
|
||||
import ru.dbotthepony.kstarbound.network.packets.clientbound.SystemShipDestroyPacket
|
||||
import ru.dbotthepony.kstarbound.network.packets.clientbound.SystemWorldStartPacket
|
||||
import ru.dbotthepony.kstarbound.network.packets.clientbound.SystemWorldUpdatePacket
|
||||
import ru.dbotthepony.kstarbound.network.packets.clientbound.TileDamageUpdatePacket
|
||||
import ru.dbotthepony.kstarbound.network.packets.clientbound.UniverseTimeUpdatePacket
|
||||
import ru.dbotthepony.kstarbound.network.packets.clientbound.UpdateWorldPropertiesPacket
|
||||
import ru.dbotthepony.kstarbound.network.packets.clientbound.WorldStartPacket
|
||||
import ru.dbotthepony.kstarbound.network.packets.clientbound.WorldStopPacket
|
||||
import ru.dbotthepony.kstarbound.network.packets.serverbound.CelestialRequestPacket
|
||||
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.EntityInteractPacket
|
||||
import ru.dbotthepony.kstarbound.network.packets.serverbound.FindUniqueEntityPacket
|
||||
import ru.dbotthepony.kstarbound.network.packets.serverbound.FlyShipPacket
|
||||
import ru.dbotthepony.kstarbound.network.packets.serverbound.PlayerWarpPacket
|
||||
import ru.dbotthepony.kstarbound.network.packets.serverbound.WorldClientStateUpdatePacket
|
||||
import ru.dbotthepony.kstarbound.network.packets.serverbound.WorldStartAcknowledgePacket
|
||||
@ -389,7 +401,7 @@ class PacketRegistry(val isLegacy: Boolean) {
|
||||
LEGACY.add(::HandshakeChallengePacket) // HandshakeChallenge
|
||||
LEGACY.add(::ChatReceivePacket)
|
||||
LEGACY.add(::UniverseTimeUpdatePacket)
|
||||
LEGACY.skip("CelestialResponse")
|
||||
LEGACY.add(::CelestialResponsePacket)
|
||||
LEGACY.add(::PlayerWarpResultPacket)
|
||||
LEGACY.skip("PlanetTypeUpdate")
|
||||
LEGACY.skip("Pause")
|
||||
@ -400,9 +412,9 @@ class PacketRegistry(val isLegacy: Boolean) {
|
||||
LEGACY.add(ClientDisconnectRequestPacket::read)
|
||||
LEGACY.add(::HandshakeResponsePacket) // HandshakeResponse
|
||||
LEGACY.add(::PlayerWarpPacket)
|
||||
LEGACY.skip("FlyShip")
|
||||
LEGACY.add(::FlyShipPacket)
|
||||
LEGACY.add(::ChatSendPacket)
|
||||
LEGACY.skip("CelestialRequest")
|
||||
LEGACY.add(::CelestialRequestPacket)
|
||||
|
||||
// Packets sent bidirectionally between the universe client and the universe
|
||||
// server
|
||||
@ -445,23 +457,23 @@ class PacketRegistry(val isLegacy: Boolean) {
|
||||
LEGACY.add(::EntityCreatePacket)
|
||||
LEGACY.add(EntityUpdateSetPacket::read)
|
||||
LEGACY.add(::EntityDestroyPacket)
|
||||
LEGACY.skip("EntityInteract")
|
||||
LEGACY.skip("EntityInteractResult")
|
||||
LEGACY.add(::EntityInteractPacket)
|
||||
LEGACY.add(::EntityInteractResultPacket)
|
||||
LEGACY.skip("HitRequest")
|
||||
LEGACY.skip("DamageRequest")
|
||||
LEGACY.skip("DamageNotification")
|
||||
LEGACY.skip("EntityMessage")
|
||||
LEGACY.skip("EntityMessageResponse")
|
||||
LEGACY.skip("UpdateWorldProperties")
|
||||
LEGACY.add(::UpdateWorldPropertiesPacket)
|
||||
LEGACY.add(::StepUpdatePacket)
|
||||
|
||||
// Packets sent system server -> system client
|
||||
LEGACY.skip("SystemWorldStart")
|
||||
LEGACY.skip("SystemWorldUpdate")
|
||||
LEGACY.skip("SystemObjectCreate")
|
||||
LEGACY.skip("SystemObjectDestroy")
|
||||
LEGACY.skip("SystemShipCreate")
|
||||
LEGACY.skip("SystemShipDestroy")
|
||||
LEGACY.add(::SystemWorldStartPacket)
|
||||
LEGACY.add(::SystemWorldUpdatePacket)
|
||||
LEGACY.add(::SystemObjectCreatePacket)
|
||||
LEGACY.add(::SystemObjectDestroyPacket)
|
||||
LEGACY.add(::SystemShipCreatePacket)
|
||||
LEGACY.add(::SystemShipDestroyPacket)
|
||||
|
||||
// Packets sent system client -> system server
|
||||
LEGACY.skip("SystemObjectSpawn")
|
||||
|
@ -0,0 +1,91 @@
|
||||
package ru.dbotthepony.kstarbound.network.packets.clientbound
|
||||
|
||||
import ru.dbotthepony.kommons.io.readCollection
|
||||
import ru.dbotthepony.kommons.io.readMap
|
||||
import ru.dbotthepony.kommons.io.readVector2i
|
||||
import ru.dbotthepony.kommons.io.readVector3i
|
||||
import ru.dbotthepony.kommons.io.writeCollection
|
||||
import ru.dbotthepony.kommons.io.writeMap
|
||||
import ru.dbotthepony.kommons.io.writeStruct2i
|
||||
import ru.dbotthepony.kommons.io.writeStruct3i
|
||||
import ru.dbotthepony.kommons.util.Either
|
||||
import ru.dbotthepony.kommons.vector.Vector2i
|
||||
import ru.dbotthepony.kommons.vector.Vector3i
|
||||
import ru.dbotthepony.kstarbound.client.ClientConnection
|
||||
import ru.dbotthepony.kstarbound.defs.world.CelestialParameters
|
||||
import ru.dbotthepony.kstarbound.io.readEither
|
||||
import ru.dbotthepony.kstarbound.io.writeEither
|
||||
import ru.dbotthepony.kstarbound.network.IClientPacket
|
||||
import java.io.DataInputStream
|
||||
import java.io.DataOutputStream
|
||||
|
||||
class CelestialResponsePacket(val responses: Collection<Either<ChunkData, SystemData>>) : IClientPacket {
|
||||
constructor(stream: DataInputStream, isLegacy: Boolean) : this(
|
||||
stream.readCollection {
|
||||
readEither(isLegacy, { ChunkData(stream, isLegacy) }, { SystemData(stream, isLegacy) })
|
||||
}
|
||||
)
|
||||
|
||||
data class ChunkData(
|
||||
val chunkIndex: Vector2i,
|
||||
// lol
|
||||
// val constellations: List<List<Pair<Vector2i, Vector2i>>>,
|
||||
val constellations: List<Pair<Vector2i, Vector2i>>,
|
||||
val systemParameters: Map<Vector3i, CelestialParameters>,
|
||||
val systemObjects: Map<Vector3i, Map<Int, PlanetData>>,
|
||||
) {
|
||||
constructor(stream: DataInputStream, isLegacy: Boolean) : this(
|
||||
stream.readVector2i(),
|
||||
if (isLegacy) stream.readCollection { readCollection { readVector2i() to readVector2i() } }.flatten() else stream.readCollection { readVector2i() to readVector2i() },
|
||||
stream.readMap({ readVector3i() }, { CelestialParameters.read(this, isLegacy) }),
|
||||
stream.readMap({ readVector3i() }, { readMap({ readInt() }, { PlanetData(this, isLegacy) }) }),
|
||||
)
|
||||
|
||||
fun write(stream: DataOutputStream, isLegacy: Boolean) {
|
||||
stream.writeStruct2i(chunkIndex)
|
||||
if (isLegacy) stream.writeByte(1) // outer List<>
|
||||
stream.writeCollection(constellations) { writeStruct2i(it.first); writeStruct2i(it.second) }
|
||||
stream.writeMap(systemParameters, { writeStruct3i(it) }, { it.write(this, isLegacy) })
|
||||
stream.writeMap(systemObjects, { writeStruct3i(it) }, { writeMap(it, { writeInt(it) }, { it.write(this, isLegacy) }) })
|
||||
}
|
||||
}
|
||||
|
||||
data class SystemData(val systemLocation: Vector3i, val planets: Map<Int, PlanetData>) {
|
||||
constructor(stream: DataInputStream, isLegacy: Boolean) : this(
|
||||
stream.readVector3i(),
|
||||
stream.readMap({ readInt() }, { PlanetData(this, isLegacy) })
|
||||
)
|
||||
|
||||
fun write(stream: DataOutputStream, isLegacy: Boolean) {
|
||||
stream.writeStruct3i(systemLocation)
|
||||
stream.writeMap(planets, { writeInt(it) }, { it.write(this, isLegacy) })
|
||||
}
|
||||
}
|
||||
|
||||
data class PlanetData(
|
||||
val planetParameters: CelestialParameters,
|
||||
val satelliteParameters: Map<Int, CelestialParameters>,
|
||||
) {
|
||||
constructor(stream: DataInputStream, isLegacy: Boolean) : this(
|
||||
CelestialParameters.read(stream, isLegacy),
|
||||
stream.readMap({ readInt() }, { CelestialParameters.read(this, isLegacy) })
|
||||
)
|
||||
|
||||
fun write(stream: DataOutputStream, isLegacy: Boolean) {
|
||||
planetParameters.write(stream, isLegacy)
|
||||
stream.writeMap(satelliteParameters, { writeInt(it) }, { it.write(this, isLegacy) })
|
||||
}
|
||||
}
|
||||
|
||||
override fun write(stream: DataOutputStream, isLegacy: Boolean) {
|
||||
// this is top-notch design, having Either<> with no value, lol
|
||||
// original sources continue to surprise me by unimaginable wonders
|
||||
stream.writeCollection(responses) {
|
||||
writeEither(it, isLegacy, { it.write(stream, isLegacy) }, { it.write(stream, isLegacy) })
|
||||
}
|
||||
}
|
||||
|
||||
override fun play(connection: ClientConnection) {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
}
|
@ -0,0 +1,34 @@
|
||||
package ru.dbotthepony.kstarbound.network.packets.clientbound
|
||||
|
||||
import ru.dbotthepony.kommons.io.readUUID
|
||||
import ru.dbotthepony.kommons.io.writeUUID
|
||||
import ru.dbotthepony.kstarbound.client.ClientConnection
|
||||
import ru.dbotthepony.kstarbound.defs.InteractAction
|
||||
import ru.dbotthepony.kstarbound.network.IClientPacket
|
||||
import ru.dbotthepony.kstarbound.network.IServerPacket
|
||||
import ru.dbotthepony.kstarbound.server.ServerConnection
|
||||
import java.io.DataInputStream
|
||||
import java.io.DataOutputStream
|
||||
import java.util.UUID
|
||||
|
||||
class EntityInteractResultPacket(val action: InteractAction, val id: UUID, val source: Int) : IServerPacket, IClientPacket {
|
||||
constructor(stream: DataInputStream, isLegacy: Boolean) : this(
|
||||
InteractAction(stream, isLegacy),
|
||||
stream.readUUID(),
|
||||
stream.readInt()
|
||||
)
|
||||
|
||||
override fun write(stream: DataOutputStream, isLegacy: Boolean) {
|
||||
action.write(stream, isLegacy)
|
||||
stream.writeUUID(id)
|
||||
stream.writeInt(source)
|
||||
}
|
||||
|
||||
override fun play(connection: ClientConnection) {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
|
||||
override fun play(connection: ServerConnection) {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
}
|
@ -0,0 +1,21 @@
|
||||
package ru.dbotthepony.kstarbound.network.packets.clientbound
|
||||
|
||||
import it.unimi.dsi.fastutil.bytes.ByteArrayList
|
||||
import ru.dbotthepony.kommons.io.readByteArray
|
||||
import ru.dbotthepony.kommons.io.writeByteArray
|
||||
import ru.dbotthepony.kstarbound.client.ClientConnection
|
||||
import ru.dbotthepony.kstarbound.network.IClientPacket
|
||||
import java.io.DataInputStream
|
||||
import java.io.DataOutputStream
|
||||
|
||||
class SystemObjectCreatePacket(val data: ByteArrayList) : IClientPacket {
|
||||
constructor(stream: DataInputStream, isLegacy: Boolean) : this(ByteArrayList.wrap(stream.readByteArray()))
|
||||
|
||||
override fun write(stream: DataOutputStream, isLegacy: Boolean) {
|
||||
stream.writeByteArray(data.elements(), 0, data.size)
|
||||
}
|
||||
|
||||
override fun play(connection: ClientConnection) {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
}
|
@ -0,0 +1,21 @@
|
||||
package ru.dbotthepony.kstarbound.network.packets.clientbound
|
||||
|
||||
import ru.dbotthepony.kommons.io.readUUID
|
||||
import ru.dbotthepony.kommons.io.writeUUID
|
||||
import ru.dbotthepony.kstarbound.client.ClientConnection
|
||||
import ru.dbotthepony.kstarbound.network.IClientPacket
|
||||
import java.io.DataInputStream
|
||||
import java.io.DataOutputStream
|
||||
import java.util.UUID
|
||||
|
||||
class SystemObjectDestroyPacket(val uuid: UUID) : IClientPacket {
|
||||
constructor(stream: DataInputStream, isLegacy: Boolean) : this(stream.readUUID())
|
||||
|
||||
override fun write(stream: DataOutputStream, isLegacy: Boolean) {
|
||||
stream.writeUUID(uuid)
|
||||
}
|
||||
|
||||
override fun play(connection: ClientConnection) {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
}
|
@ -0,0 +1,21 @@
|
||||
package ru.dbotthepony.kstarbound.network.packets.clientbound
|
||||
|
||||
import it.unimi.dsi.fastutil.bytes.ByteArrayList
|
||||
import ru.dbotthepony.kommons.io.readByteArray
|
||||
import ru.dbotthepony.kommons.io.writeByteArray
|
||||
import ru.dbotthepony.kstarbound.client.ClientConnection
|
||||
import ru.dbotthepony.kstarbound.network.IClientPacket
|
||||
import java.io.DataInputStream
|
||||
import java.io.DataOutputStream
|
||||
|
||||
class SystemShipCreatePacket(val data: ByteArrayList) : IClientPacket {
|
||||
constructor(stream: DataInputStream, isLegacy: Boolean) : this(ByteArrayList.wrap(stream.readByteArray()))
|
||||
|
||||
override fun write(stream: DataOutputStream, isLegacy: Boolean) {
|
||||
stream.writeByteArray(data.elements(), 0, data.size)
|
||||
}
|
||||
|
||||
override fun play(connection: ClientConnection) {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
}
|
@ -0,0 +1,21 @@
|
||||
package ru.dbotthepony.kstarbound.network.packets.clientbound
|
||||
|
||||
import ru.dbotthepony.kommons.io.readUUID
|
||||
import ru.dbotthepony.kommons.io.writeUUID
|
||||
import ru.dbotthepony.kstarbound.client.ClientConnection
|
||||
import ru.dbotthepony.kstarbound.network.IClientPacket
|
||||
import java.io.DataInputStream
|
||||
import java.io.DataOutputStream
|
||||
import java.util.UUID
|
||||
|
||||
class SystemShipDestroyPacket(val uuid: UUID) : IClientPacket {
|
||||
constructor(stream: DataInputStream, isLegacy: Boolean) : this(stream.readUUID())
|
||||
|
||||
override fun write(stream: DataOutputStream, isLegacy: Boolean) {
|
||||
stream.writeUUID(uuid)
|
||||
}
|
||||
|
||||
override fun play(connection: ClientConnection) {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
}
|
@ -0,0 +1,40 @@
|
||||
package ru.dbotthepony.kstarbound.network.packets.clientbound
|
||||
|
||||
import it.unimi.dsi.fastutil.bytes.ByteArrayList
|
||||
import ru.dbotthepony.kommons.io.readByteArray
|
||||
import ru.dbotthepony.kommons.io.readCollection
|
||||
import ru.dbotthepony.kommons.io.readUUID
|
||||
import ru.dbotthepony.kommons.io.readVector3i
|
||||
import ru.dbotthepony.kommons.io.writeByteArray
|
||||
import ru.dbotthepony.kommons.io.writeCollection
|
||||
import ru.dbotthepony.kommons.io.writeStruct3i
|
||||
import ru.dbotthepony.kommons.io.writeUUID
|
||||
import ru.dbotthepony.kommons.vector.Vector3i
|
||||
import ru.dbotthepony.kstarbound.client.ClientConnection
|
||||
import ru.dbotthepony.kstarbound.network.IClientPacket
|
||||
import ru.dbotthepony.kstarbound.world.SystemWorldLocation
|
||||
import java.io.DataInputStream
|
||||
import java.io.DataOutputStream
|
||||
import java.util.UUID
|
||||
|
||||
class SystemWorldStartPacket(val location: Vector3i, val objects: Collection<ByteArrayList>, val ships: Collection<ByteArrayList>, val shipUUID: UUID, val shipLocation: SystemWorldLocation) : IClientPacket {
|
||||
constructor(stream: DataInputStream, isLegacy: Boolean) : this(
|
||||
stream.readVector3i(),
|
||||
stream.readCollection { ByteArrayList.wrap(readByteArray()) },
|
||||
stream.readCollection { ByteArrayList.wrap(readByteArray()) },
|
||||
stream.readUUID(),
|
||||
SystemWorldLocation.read(stream, isLegacy)
|
||||
)
|
||||
|
||||
override fun write(stream: DataOutputStream, isLegacy: Boolean) {
|
||||
stream.writeStruct3i(location)
|
||||
stream.writeCollection(objects) { writeByteArray(it.elements(), 0, it.size) }
|
||||
stream.writeCollection(ships) { writeByteArray(it.elements(), 0, it.size) }
|
||||
stream.writeUUID(shipUUID)
|
||||
shipLocation.write(stream, isLegacy)
|
||||
}
|
||||
|
||||
override fun play(connection: ClientConnection) {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
}
|
@ -0,0 +1,30 @@
|
||||
package ru.dbotthepony.kstarbound.network.packets.clientbound
|
||||
|
||||
import it.unimi.dsi.fastutil.bytes.ByteArrayList
|
||||
import ru.dbotthepony.kommons.io.readByteArray
|
||||
import ru.dbotthepony.kommons.io.readMap
|
||||
import ru.dbotthepony.kommons.io.readUUID
|
||||
import ru.dbotthepony.kommons.io.writeByteArray
|
||||
import ru.dbotthepony.kommons.io.writeMap
|
||||
import ru.dbotthepony.kommons.io.writeUUID
|
||||
import ru.dbotthepony.kstarbound.client.ClientConnection
|
||||
import ru.dbotthepony.kstarbound.network.IClientPacket
|
||||
import java.io.DataInputStream
|
||||
import java.io.DataOutputStream
|
||||
import java.util.UUID
|
||||
|
||||
class SystemWorldUpdatePacket(val objects: Map<UUID, ByteArrayList>, val ships: Map<UUID, ByteArrayList>) : IClientPacket {
|
||||
constructor(stream: DataInputStream, isLegacy: Boolean) : this(
|
||||
stream.readMap({ readUUID() }, { ByteArrayList.wrap(readByteArray()) }),
|
||||
stream.readMap({ readUUID() }, { ByteArrayList.wrap(readByteArray()) })
|
||||
)
|
||||
|
||||
override fun write(stream: DataOutputStream, isLegacy: Boolean) {
|
||||
stream.writeMap(objects, { writeUUID(it) }, { writeByteArray(it.elements(), 0, it.size) })
|
||||
stream.writeMap(ships, { writeUUID(it) }, { writeByteArray(it.elements(), 0, it.size) })
|
||||
}
|
||||
|
||||
override fun play(connection: ClientConnection) {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
}
|
@ -0,0 +1,34 @@
|
||||
package ru.dbotthepony.kstarbound.network.packets.clientbound
|
||||
|
||||
import com.google.gson.JsonObject
|
||||
import ru.dbotthepony.kommons.gson.set
|
||||
import ru.dbotthepony.kstarbound.client.ClientConnection
|
||||
import ru.dbotthepony.kstarbound.json.mergeJson
|
||||
import ru.dbotthepony.kstarbound.json.readJsonObject
|
||||
import ru.dbotthepony.kstarbound.json.writeJsonObject
|
||||
import ru.dbotthepony.kstarbound.network.IClientPacket
|
||||
import ru.dbotthepony.kstarbound.network.IServerPacket
|
||||
import ru.dbotthepony.kstarbound.server.ServerConnection
|
||||
import java.io.DataInputStream
|
||||
import java.io.DataOutputStream
|
||||
|
||||
class UpdateWorldPropertiesPacket(val update: JsonObject) : IClientPacket, IServerPacket {
|
||||
constructor(stream: DataInputStream, isLegacy: Boolean) : this(stream.readJsonObject())
|
||||
|
||||
override fun write(stream: DataOutputStream, isLegacy: Boolean) {
|
||||
stream.writeJsonObject(update)
|
||||
}
|
||||
|
||||
override fun play(connection: ClientConnection) {
|
||||
connection.enqueue {
|
||||
world?.updateProperties(update)
|
||||
}
|
||||
}
|
||||
|
||||
override fun play(connection: ServerConnection) {
|
||||
connection.enqueue {
|
||||
updateProperties(update)
|
||||
broadcast(this@UpdateWorldPropertiesPacket)
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,62 @@
|
||||
package ru.dbotthepony.kstarbound.network.packets.serverbound
|
||||
|
||||
import kotlinx.coroutines.launch
|
||||
import ru.dbotthepony.kommons.io.readCollection
|
||||
import ru.dbotthepony.kommons.io.readVector2i
|
||||
import ru.dbotthepony.kommons.io.readVector3i
|
||||
import ru.dbotthepony.kommons.io.writeCollection
|
||||
import ru.dbotthepony.kommons.io.writeStruct2i
|
||||
import ru.dbotthepony.kommons.io.writeStruct3i
|
||||
import ru.dbotthepony.kommons.util.Either
|
||||
import ru.dbotthepony.kommons.vector.Vector2i
|
||||
import ru.dbotthepony.kommons.vector.Vector3i
|
||||
import ru.dbotthepony.kstarbound.defs.world.CelestialParameters
|
||||
import ru.dbotthepony.kstarbound.io.readEither
|
||||
import ru.dbotthepony.kstarbound.io.writeEither
|
||||
import ru.dbotthepony.kstarbound.network.IServerPacket
|
||||
import ru.dbotthepony.kstarbound.network.packets.clientbound.CelestialResponsePacket
|
||||
import ru.dbotthepony.kstarbound.server.ServerConnection
|
||||
import ru.dbotthepony.kstarbound.world.UniversePos
|
||||
import java.io.DataInputStream
|
||||
import java.io.DataOutputStream
|
||||
|
||||
class CelestialRequestPacket(val requests: Collection<Either<Vector2i, Vector3i>>) : IServerPacket {
|
||||
constructor(stream: DataInputStream, isLegacy: Boolean) : this(stream.readCollection { readEither(isLegacy, { readVector2i() }, { readVector3i() }) })
|
||||
|
||||
override fun write(stream: DataOutputStream, isLegacy: Boolean) {
|
||||
stream.writeCollection(requests) {
|
||||
writeEither(it, isLegacy, { writeStruct2i(it) }, { writeStruct3i(it) })
|
||||
}
|
||||
}
|
||||
|
||||
override fun play(connection: ServerConnection) {
|
||||
connection.scope.launch {
|
||||
val responses = ArrayList<Either<CelestialResponsePacket.ChunkData, CelestialResponsePacket.SystemData>>()
|
||||
|
||||
for (request in requests) {
|
||||
if (request.isLeft) {
|
||||
val chunkPos = request.left()
|
||||
responses.add(Either.left(connection.server.universe.getChunk(chunkPos)?.toNetwork() ?: continue))
|
||||
} else {
|
||||
val systemPos = UniversePos(request.right())
|
||||
val map = HashMap<Int, CelestialResponsePacket.PlanetData>()
|
||||
|
||||
for (planet in connection.server.universe.children(systemPos)) {
|
||||
val planetData = connection.server.universe.parameters(planet) ?: continue
|
||||
val children = HashMap<Int, CelestialParameters>()
|
||||
|
||||
for (satellite in connection.server.universe.children(planet)) {
|
||||
children[satellite.satelliteOrbit] = connection.server.universe.parameters(satellite) ?: continue
|
||||
}
|
||||
|
||||
map[planet.planetOrbit] = CelestialResponsePacket.PlanetData(planetData, children)
|
||||
}
|
||||
|
||||
responses.add(Either.right(CelestialResponsePacket.SystemData(systemPos.location, map)))
|
||||
}
|
||||
}
|
||||
|
||||
connection.send(CelestialResponsePacket(responses))
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,47 @@
|
||||
package ru.dbotthepony.kstarbound.network.packets.serverbound
|
||||
|
||||
import ru.dbotthepony.kommons.io.readUUID
|
||||
import ru.dbotthepony.kommons.io.writeUUID
|
||||
import ru.dbotthepony.kstarbound.client.ClientConnection
|
||||
import ru.dbotthepony.kstarbound.defs.InteractAction
|
||||
import ru.dbotthepony.kstarbound.defs.InteractRequest
|
||||
import ru.dbotthepony.kstarbound.network.Connection
|
||||
import ru.dbotthepony.kstarbound.network.IClientPacket
|
||||
import ru.dbotthepony.kstarbound.network.IServerPacket
|
||||
import ru.dbotthepony.kstarbound.network.packets.clientbound.EntityInteractResultPacket
|
||||
import ru.dbotthepony.kstarbound.server.ServerConnection
|
||||
import java.io.DataInputStream
|
||||
import java.io.DataOutputStream
|
||||
import java.util.UUID
|
||||
|
||||
class EntityInteractPacket(val request: InteractRequest, val id: UUID) : IServerPacket, IClientPacket {
|
||||
constructor(stream: DataInputStream, isLegacy: Boolean) : this(
|
||||
InteractRequest(stream, isLegacy),
|
||||
stream.readUUID()
|
||||
)
|
||||
|
||||
override fun write(stream: DataOutputStream, isLegacy: Boolean) {
|
||||
request.write(stream, isLegacy)
|
||||
stream.writeUUID(id)
|
||||
}
|
||||
|
||||
override fun play(connection: ServerConnection) {
|
||||
if (request.target >= 0) {
|
||||
connection.enqueue {
|
||||
connection.send(EntityInteractResultPacket(entities[request.target]?.interact(request) ?: InteractAction.NONE, id, request.source))
|
||||
}
|
||||
} else {
|
||||
val other = connection.server.channels.connectionByID(Connection.connectionForEntityID(request.target)) ?: throw IllegalArgumentException("No such connection ID ${Connection.connectionForEntityID(request.target)} for EntityInteractPacket")
|
||||
|
||||
if (other == connection) {
|
||||
throw IllegalStateException("Attempt to interact with own entity through server?")
|
||||
}
|
||||
|
||||
other.send(this)
|
||||
}
|
||||
}
|
||||
|
||||
override fun play(connection: ClientConnection) {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
}
|
@ -0,0 +1,23 @@
|
||||
package ru.dbotthepony.kstarbound.network.packets.serverbound
|
||||
|
||||
import ru.dbotthepony.kommons.io.readVector3i
|
||||
import ru.dbotthepony.kommons.io.writeStruct3i
|
||||
import ru.dbotthepony.kommons.vector.Vector3i
|
||||
import ru.dbotthepony.kstarbound.network.IServerPacket
|
||||
import ru.dbotthepony.kstarbound.server.ServerConnection
|
||||
import ru.dbotthepony.kstarbound.world.SystemWorldLocation
|
||||
import java.io.DataInputStream
|
||||
import java.io.DataOutputStream
|
||||
|
||||
class FlyShipPacket(val system: Vector3i, val location: SystemWorldLocation = SystemWorldLocation.Transit) : IServerPacket {
|
||||
constructor(stream: DataInputStream, isLegacy: Boolean) : this(stream.readVector3i(), SystemWorldLocation.read(stream, isLegacy))
|
||||
|
||||
override fun write(stream: DataOutputStream, isLegacy: Boolean) {
|
||||
stream.writeStruct3i(system)
|
||||
location.write(stream, isLegacy)
|
||||
}
|
||||
|
||||
override fun play(connection: ServerConnection) {
|
||||
connection.flyShip(system, location)
|
||||
}
|
||||
}
|
@ -160,7 +160,7 @@ fun <E : Enum<E>> networkedEnum(value: E) = BasicNetworkedElement(value, StreamC
|
||||
// networks enum as a signed variable length integer on legacy protocol
|
||||
fun <E : Enum<E>> networkedEnumStupid(value: E): BasicNetworkedElement<E, Int> {
|
||||
val codec = StreamCodec.Enum(value::class.java)
|
||||
return BasicNetworkedElement(value, codec, VarIntValueCodec, { it.ordinal.shl(1) }, { codec.values[it.ushr(1)] })
|
||||
return BasicNetworkedElement(value, codec, VarIntValueCodec, { it.ordinal }, { codec.values[it] })
|
||||
}
|
||||
|
||||
// networks enum as string on legacy protocol
|
||||
|
@ -45,6 +45,10 @@ class ServerChannels(val server: StarboundServer) : Closeable {
|
||||
broadcast(ServerInfoPacket(new, server.settings.maxPlayers))
|
||||
}
|
||||
|
||||
fun connectionByID(id: Int): ServerConnection? {
|
||||
return connections.firstOrNull { it.connectionID == id }
|
||||
}
|
||||
|
||||
private fun cycleConnectionID(): Int {
|
||||
val v = ++nextConnectionID and MAX_PLAYERS
|
||||
|
||||
|
@ -3,16 +3,20 @@ package ru.dbotthepony.kstarbound.server
|
||||
import com.google.gson.JsonObject
|
||||
import io.netty.channel.ChannelHandlerContext
|
||||
import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.channels.Channel
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.future.await
|
||||
import kotlinx.coroutines.launch
|
||||
import org.apache.logging.log4j.LogManager
|
||||
import ru.dbotthepony.kommons.io.ByteKey
|
||||
import ru.dbotthepony.kommons.util.KOptional
|
||||
import ru.dbotthepony.kommons.vector.Vector3i
|
||||
import ru.dbotthepony.kstarbound.GlobalDefaults
|
||||
import ru.dbotthepony.kstarbound.defs.WarpAction
|
||||
import ru.dbotthepony.kstarbound.defs.WarpAlias
|
||||
import ru.dbotthepony.kstarbound.defs.WorldID
|
||||
import ru.dbotthepony.kstarbound.defs.world.SkyType
|
||||
import ru.dbotthepony.kstarbound.defs.world.VisitableWorldParameters
|
||||
import ru.dbotthepony.kstarbound.network.Connection
|
||||
import ru.dbotthepony.kstarbound.network.ConnectionSide
|
||||
import ru.dbotthepony.kstarbound.network.ConnectionType
|
||||
@ -23,11 +27,12 @@ import ru.dbotthepony.kstarbound.network.packets.clientbound.ServerDisconnectPac
|
||||
import ru.dbotthepony.kstarbound.server.world.ServerWorldTracker
|
||||
import ru.dbotthepony.kstarbound.server.world.WorldStorage
|
||||
import ru.dbotthepony.kstarbound.server.world.LegacyWorldStorage
|
||||
import ru.dbotthepony.kstarbound.server.world.ServerSystemWorld
|
||||
import ru.dbotthepony.kstarbound.server.world.ServerWorld
|
||||
import ru.dbotthepony.kstarbound.world.SystemWorldLocation
|
||||
import ru.dbotthepony.kstarbound.world.UniversePos
|
||||
import java.util.HashMap
|
||||
import java.util.UUID
|
||||
import java.util.concurrent.CompletableFuture
|
||||
import java.util.concurrent.TimeUnit
|
||||
import kotlin.properties.Delegates
|
||||
|
||||
// serverside part of connection
|
||||
@ -35,13 +40,16 @@ class ServerConnection(val server: StarboundServer, type: ConnectionType) : Conn
|
||||
var tracker: ServerWorldTracker? = null
|
||||
var worldStartAcknowledged = false
|
||||
var returnWarp: WarpAction? = null
|
||||
var systemWorld: ServerSystemWorld? = null
|
||||
|
||||
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)
|
||||
fun enqueue(task: ServerWorld.() -> Unit) {
|
||||
return tracker?.enqueue(task) ?: throw IllegalStateException("Not in world.")
|
||||
}
|
||||
|
||||
lateinit var shipWorld: ServerWorld
|
||||
private set
|
||||
@ -62,6 +70,10 @@ class ServerConnection(val server: StarboundServer, type: ConnectionType) : Conn
|
||||
return "ServerConnection[$nickname $uuid ID=$connectionID channel=$channel / $world]"
|
||||
}
|
||||
|
||||
fun alias(): String {
|
||||
return "$nickname <$connectionID/$uuid>"
|
||||
}
|
||||
|
||||
private val shipChunks = HashMap<ByteKey, KOptional<ByteArray>>()
|
||||
private val modifiedShipChunks = ObjectOpenHashSet<ByteKey>()
|
||||
var shipChunkSource by Delegates.notNull<WorldStorage>()
|
||||
@ -127,9 +139,12 @@ class ServerConnection(val server: StarboundServer, type: ConnectionType) : Conn
|
||||
|
||||
private val warpQueue = Channel<Pair<WarpAction, Boolean>>(capacity = 10)
|
||||
|
||||
private suspend fun handleWarps() {
|
||||
private suspend fun warpEventLoop() {
|
||||
while (true) {
|
||||
val (request, deploy) = warpQueue.receive()
|
||||
var (request, deploy) = warpQueue.receive()
|
||||
|
||||
if (request is WarpAlias)
|
||||
request = request.remap(this)
|
||||
|
||||
LOGGER.info("Trying to warp $this to $request")
|
||||
|
||||
@ -163,6 +178,139 @@ class ServerConnection(val server: StarboundServer, type: ConnectionType) : Conn
|
||||
}
|
||||
}
|
||||
|
||||
private val flyShipQueue = Channel<Pair<Vector3i, SystemWorldLocation>>(capacity = 40)
|
||||
|
||||
fun flyShip(system: Vector3i, location: SystemWorldLocation) {
|
||||
flyShipQueue.trySend(system to location)
|
||||
}
|
||||
|
||||
// coordinates ship flights between systems
|
||||
private suspend fun shipFlightEventLoop() {
|
||||
shipWorld.sky.startFlying(true, true)
|
||||
var visited = 0
|
||||
|
||||
LOGGER.info("Finding starter world for ${alias()}...")
|
||||
val params = GlobalDefaults.universeServer.findStarterWorldParameters
|
||||
|
||||
// visit all since sparsingly trying to find specific world is not healthy performance wise
|
||||
var found = server.universe.findRandomWorld(params.tries, params.range, visitAll = true, predicate = {
|
||||
if (++visited % 600 == 0) {
|
||||
LOGGER.info("Still finding starter world for ${alias()}...")
|
||||
}
|
||||
|
||||
val parameters = server.universe.parameters(it) ?: return@findRandomWorld false
|
||||
if (parameters.visitableParameters == null) return@findRandomWorld false
|
||||
if (!params.starterWorld.test(parameters.visitableParameters!!)) return@findRandomWorld false
|
||||
|
||||
val children = ArrayList<VisitableWorldParameters>()
|
||||
|
||||
for (child in server.universe.children(it.system())) {
|
||||
val p = server.universe.parameters(child)
|
||||
|
||||
if (p?.visitableParameters != null) {
|
||||
children.add(p.visitableParameters!!)
|
||||
}
|
||||
|
||||
for (child2 in server.universe.children(child)) {
|
||||
val p2 = server.universe.parameters(child2)
|
||||
|
||||
if (p2?.visitableParameters != null) {
|
||||
children.add(p2.visitableParameters!!)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
params.requiredSystemWorlds.all { predicate -> children.any { predicate.test(it) } }
|
||||
})
|
||||
|
||||
if (found == null) {
|
||||
LOGGER.fatal("Unable to find starter world for $this!")
|
||||
disconnect("Unable to find starter world")
|
||||
return
|
||||
}
|
||||
|
||||
LOGGER.info("Found appropriate starter world at $found for ${alias()}")
|
||||
|
||||
var world = server.loadSystemWorld(found.location).await()
|
||||
var ship = world.addClient(this).await()
|
||||
shipWorld.sky.stopFlyingAt(ship.location.skyParameters(world))
|
||||
shipCoordinate = found
|
||||
|
||||
var currentFlightJob: Job? = null
|
||||
|
||||
while (true) {
|
||||
val (system, location) = flyShipQueue.receive()
|
||||
|
||||
val currentSystem = systemWorld
|
||||
|
||||
if (system == currentSystem?.location) {
|
||||
// fly ship in current system
|
||||
currentFlightJob?.cancel()
|
||||
val flight = currentSystem.flyShip(this, location)
|
||||
|
||||
shipWorld.mailbox.execute {
|
||||
shipWorld.sky.startFlying(false)
|
||||
}
|
||||
|
||||
currentFlightJob = scope.launch {
|
||||
val coords = flight.await()
|
||||
val action = coords.orbitalAction(currentSystem)
|
||||
orbitalWarpAction = action
|
||||
|
||||
for (client in shipWorld.clients) {
|
||||
client.client.orbitalWarpAction = action
|
||||
}
|
||||
|
||||
val sky = coords.skyParameters(world)
|
||||
|
||||
shipWorld.mailbox.execute {
|
||||
shipWorld.sky.stopFlyingAt(sky)
|
||||
}
|
||||
}
|
||||
|
||||
orbitalWarpAction = KOptional()
|
||||
|
||||
for (client in shipWorld.clients) {
|
||||
client.client.orbitalWarpAction = KOptional()
|
||||
}
|
||||
} else {
|
||||
// we need to travel to other system
|
||||
val exists = server.universe.parameters(UniversePos(system)) != null
|
||||
|
||||
if (!exists)
|
||||
continue
|
||||
|
||||
currentFlightJob?.cancel()
|
||||
|
||||
shipWorld.mailbox.execute {
|
||||
shipWorld.sky.startFlying(true)
|
||||
}
|
||||
|
||||
LOGGER.info("${alias()} is flying to new system: ${UniversePos(system)}")
|
||||
val newSystem = server.loadSystemWorld(system)
|
||||
|
||||
shipCoordinate = UniversePos(system)
|
||||
|
||||
orbitalWarpAction = KOptional()
|
||||
|
||||
for (client in shipWorld.clients) {
|
||||
client.client.orbitalWarpAction = KOptional()
|
||||
}
|
||||
|
||||
delay((GlobalDefaults.universeServer.queuedFlightWaitTime * 1000L).toLong())
|
||||
|
||||
world = newSystem.await()
|
||||
ship = world.addClient(this).await()
|
||||
|
||||
val newParams = ship.location.skyParameters(world)
|
||||
|
||||
shipWorld.mailbox.execute {
|
||||
shipWorld.sky.stopFlyingAt(newParams)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun enqueueWarp(destination: WarpAction, deploy: Boolean = false) {
|
||||
warpQueue.trySend(destination to deploy)
|
||||
}
|
||||
@ -242,7 +390,11 @@ class ServerConnection(val server: StarboundServer, type: ConnectionType) : Conn
|
||||
shipWorld = it
|
||||
shipWorld.thread.start()
|
||||
enqueueWarp(WarpAlias.OwnShip)
|
||||
coroutineScope.launch { handleWarps() }
|
||||
shipUpgrades = shipUpgrades.addCapability("planetTravel")
|
||||
shipUpgrades = shipUpgrades.addCapability("teleport")
|
||||
shipUpgrades = shipUpgrades.copy(maxFuel = 10000, shipLevel = 1)
|
||||
scope.launch { warpEventLoop() }
|
||||
scope.launch { shipFlightEventLoop() }
|
||||
|
||||
if (server.channels.connections.size > 1) {
|
||||
enqueueWarp(WarpAction.Player(server.channels.connections.first().uuid!!))
|
||||
|
@ -1,25 +1,36 @@
|
||||
package ru.dbotthepony.kstarbound.server
|
||||
|
||||
import it.unimi.dsi.fastutil.objects.ObjectArraySet
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.async
|
||||
import kotlinx.coroutines.cancel
|
||||
import kotlinx.coroutines.future.asCompletableFuture
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import org.apache.logging.log4j.LogManager
|
||||
import ru.dbotthepony.kommons.util.MailboxExecutorService
|
||||
import ru.dbotthepony.kommons.vector.Vector3i
|
||||
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
|
||||
import ru.dbotthepony.kstarbound.server.world.ServerSystemWorld
|
||||
import ru.dbotthepony.kstarbound.util.Clock
|
||||
import ru.dbotthepony.kstarbound.util.ExceptionLogger
|
||||
import ru.dbotthepony.kstarbound.util.ExecutionSpinner
|
||||
import ru.dbotthepony.kstarbound.world.UniversePos
|
||||
import java.io.Closeable
|
||||
import java.io.File
|
||||
import java.util.UUID
|
||||
import java.util.concurrent.CompletableFuture
|
||||
import java.util.concurrent.ConcurrentHashMap
|
||||
import java.util.concurrent.CopyOnWriteArrayList
|
||||
import java.util.concurrent.TimeUnit
|
||||
import java.util.concurrent.atomic.AtomicInteger
|
||||
import java.util.concurrent.locks.ReentrantLock
|
||||
import java.util.function.Supplier
|
||||
|
||||
sealed class StarboundServer(val root: File) : Closeable {
|
||||
init {
|
||||
@ -38,6 +49,25 @@ sealed class StarboundServer(val root: File) : Closeable {
|
||||
val thread = Thread(spinner, "Server Thread")
|
||||
val universe = ServerUniverse()
|
||||
val chat = ChatHandler(this)
|
||||
val context = CoroutineScope(Starbound.COROUTINE_EXECUTOR)
|
||||
|
||||
private val systemWorlds = HashMap<Vector3i, CompletableFuture<ServerSystemWorld>>()
|
||||
|
||||
private suspend fun loadSystemWorld0(location: Vector3i): ServerSystemWorld {
|
||||
return ServerSystemWorld.create(this, location)
|
||||
}
|
||||
|
||||
fun loadSystemWorld(location: Vector3i): CompletableFuture<ServerSystemWorld> {
|
||||
return CompletableFuture.supplyAsync(Supplier {
|
||||
systemWorlds.computeIfAbsent(location) {
|
||||
context.async { loadSystemWorld0(location) }.asCompletableFuture()
|
||||
}
|
||||
}, mailbox).thenCompose { it }
|
||||
}
|
||||
|
||||
fun loadSystemWorld(location: UniversePos): CompletableFuture<ServerSystemWorld> {
|
||||
return loadSystemWorld(location.location)
|
||||
}
|
||||
|
||||
val settings = ServerSettings()
|
||||
val channels = ServerChannels(this)
|
||||
@ -108,6 +138,29 @@ sealed class StarboundServer(val root: File) : Closeable {
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: schedule to thread pool?
|
||||
// right now, system worlds are rather lightweight, and having separate threads for them is overkill
|
||||
runBlocking {
|
||||
systemWorlds.values.removeIf {
|
||||
if (it.isCompletedExceptionally) {
|
||||
return@removeIf true
|
||||
}
|
||||
|
||||
if (!it.isDone) {
|
||||
return@removeIf false
|
||||
}
|
||||
|
||||
launch { it.get().tick() }
|
||||
|
||||
if (it.get().shouldClose()) {
|
||||
LOGGER.info("Stopping idling ${it.get()}")
|
||||
return@removeIf true
|
||||
}
|
||||
|
||||
return@removeIf false
|
||||
}
|
||||
}
|
||||
|
||||
tick0()
|
||||
return !isClosed
|
||||
}
|
||||
@ -116,6 +169,7 @@ sealed class StarboundServer(val root: File) : Closeable {
|
||||
if (isClosed) return
|
||||
isClosed = true
|
||||
|
||||
context.cancel("Server shutting down")
|
||||
channels.close()
|
||||
worlds.values.forEach { it.close() }
|
||||
limboWorlds.forEach { it.close() }
|
||||
|
@ -0,0 +1,516 @@
|
||||
package ru.dbotthepony.kstarbound.server.world
|
||||
|
||||
import com.google.common.collect.ImmutableList
|
||||
import com.google.gson.JsonElement
|
||||
import com.google.gson.JsonObject
|
||||
import it.unimi.dsi.fastutil.bytes.ByteArrayList
|
||||
import it.unimi.dsi.fastutil.io.FastByteArrayOutputStream
|
||||
import it.unimi.dsi.fastutil.objects.Object2LongOpenHashMap
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.cancel
|
||||
import org.apache.logging.log4j.LogManager
|
||||
import ru.dbotthepony.kommons.io.writeBinaryString
|
||||
import ru.dbotthepony.kommons.io.writeUUID
|
||||
import ru.dbotthepony.kommons.util.KOptional
|
||||
import ru.dbotthepony.kommons.vector.Vector2d
|
||||
import ru.dbotthepony.kommons.vector.Vector3i
|
||||
import ru.dbotthepony.kstarbound.GlobalDefaults
|
||||
import ru.dbotthepony.kstarbound.Starbound
|
||||
import ru.dbotthepony.kstarbound.defs.world.SystemWorldObjectConfig
|
||||
import ru.dbotthepony.kstarbound.io.writeStruct2d
|
||||
import ru.dbotthepony.kstarbound.json.builder.JsonFactory
|
||||
import ru.dbotthepony.kstarbound.json.writeJsonObject
|
||||
import ru.dbotthepony.kstarbound.network.packets.clientbound.SystemObjectCreatePacket
|
||||
import ru.dbotthepony.kstarbound.network.packets.clientbound.SystemObjectDestroyPacket
|
||||
import ru.dbotthepony.kstarbound.network.packets.clientbound.SystemShipCreatePacket
|
||||
import ru.dbotthepony.kstarbound.network.packets.clientbound.SystemShipDestroyPacket
|
||||
import ru.dbotthepony.kstarbound.network.packets.clientbound.SystemWorldStartPacket
|
||||
import ru.dbotthepony.kstarbound.network.packets.clientbound.SystemWorldUpdatePacket
|
||||
import ru.dbotthepony.kstarbound.server.ServerConnection
|
||||
import ru.dbotthepony.kstarbound.server.StarboundServer
|
||||
import ru.dbotthepony.kstarbound.util.random.nextRange
|
||||
import ru.dbotthepony.kstarbound.util.random.random
|
||||
import ru.dbotthepony.kstarbound.util.random.staticRandom64
|
||||
import ru.dbotthepony.kstarbound.world.SystemWorld
|
||||
import ru.dbotthepony.kstarbound.world.SystemWorldLocation
|
||||
import ru.dbotthepony.kstarbound.world.UniversePos
|
||||
import java.io.Closeable
|
||||
import java.io.DataOutputStream
|
||||
import java.util.UUID
|
||||
import java.util.concurrent.CompletableFuture
|
||||
import java.util.concurrent.ConcurrentLinkedQueue
|
||||
import java.util.concurrent.atomic.AtomicBoolean
|
||||
import java.util.function.Supplier
|
||||
import kotlin.math.PI
|
||||
import kotlin.math.cos
|
||||
import kotlin.math.sin
|
||||
|
||||
class ServerSystemWorld : SystemWorld {
|
||||
@JsonFactory
|
||||
data class JsonData(
|
||||
val location: Vector3i,
|
||||
val lastSpawn: Double,
|
||||
val objectSpawnTime: Double,
|
||||
val objects: ImmutableList<JsonObject> = ImmutableList.of(),
|
||||
)
|
||||
|
||||
override val entities = HashMap<UUID, ServerEntity>()
|
||||
override val ships = HashMap<UUID, ServerShip>()
|
||||
|
||||
private class Task<T>(val supplier: Supplier<T>) : Runnable {
|
||||
val future = CompletableFuture<T>()
|
||||
|
||||
override fun run() {
|
||||
try {
|
||||
future.complete(supplier.get())
|
||||
} catch (err: Throwable) {
|
||||
future.completeExceptionally(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private val tasks = ConcurrentLinkedQueue<Task<*>>()
|
||||
|
||||
override fun toString(): String {
|
||||
return "ServerSystemWorld at $systemLocation"
|
||||
}
|
||||
|
||||
@Suppress("NAME_SHADOWING")
|
||||
private fun addClient0(client: ServerConnection, shipSpeed: Double, location: SystemWorldLocation): ServerShip {
|
||||
if (!client.isConnected || client.uuid == null)
|
||||
throw IllegalStateException("Trying to add disconnected client, or client without UUID: $client")
|
||||
|
||||
if (client.uuid!! in ships)
|
||||
throw IllegalStateException("Already has client $client in $this!")
|
||||
|
||||
var location = location
|
||||
|
||||
if (location is SystemWorldLocation.Entity && location.uuid !in entities)
|
||||
location = SystemWorldLocation.Transit
|
||||
|
||||
if (location == SystemWorldLocation.Transit)
|
||||
location = SystemWorldLocation.Position(randomArrivalPosition())
|
||||
|
||||
client.systemWorld?.removeClient(client)
|
||||
client.systemWorld = this
|
||||
|
||||
val objects = entities.values.map { it.writeNetwork(client.isLegacy) }
|
||||
val ships = ships.values.map { it.writeNetwork(client.isLegacy) }
|
||||
|
||||
val ship = ServerShip(client, location)
|
||||
ship.speed = shipSpeed
|
||||
|
||||
client.send(SystemWorldStartPacket(this.location, objects, ships, client.uuid!!, location))
|
||||
|
||||
val legacyPacket by lazy { SystemShipCreatePacket(ship.writeNetwork(true)) }
|
||||
val nativePacket by lazy { SystemShipCreatePacket(ship.writeNetwork(false)) }
|
||||
|
||||
for (otherShip in this.ships.values) {
|
||||
if (otherShip != ship) {
|
||||
otherShip.client.send(if (otherShip.client.isLegacy) legacyPacket else nativePacket)
|
||||
}
|
||||
}
|
||||
|
||||
return ship
|
||||
}
|
||||
|
||||
private fun removeClient0(client: ServerConnection) {
|
||||
val ship = ships.remove(client.uuid) ?: throw IllegalStateException("No client $client in $this!")
|
||||
|
||||
val packet = SystemShipDestroyPacket(ship.uuid)
|
||||
ships.values.forEach { it.client.send(packet) }
|
||||
}
|
||||
|
||||
fun addClient(client: ServerConnection, shipSpeed: Double = GlobalDefaults.systemWorld.clientShip.speed, location: SystemWorldLocation = SystemWorldLocation.Transit): CompletableFuture<ServerShip> {
|
||||
val task = Task { addClient0(client, shipSpeed, location) }
|
||||
tasks.add(task)
|
||||
return task.future
|
||||
}
|
||||
|
||||
fun removeClient(client: ServerConnection): CompletableFuture<Unit> {
|
||||
val task = Task { removeClient0(client) }
|
||||
tasks.add(task)
|
||||
return task.future
|
||||
}
|
||||
|
||||
private fun flyShip0(client: ServerConnection, location: SystemWorldLocation, future: CompletableFuture<SystemWorldLocation>) {
|
||||
val ship = ships[client.uuid] ?: throw IllegalStateException("No client $client in $this!")
|
||||
ship.destination(location, future)
|
||||
}
|
||||
|
||||
fun flyShip(client: ServerConnection, location: SystemWorldLocation): CompletableFuture<SystemWorldLocation> {
|
||||
val future = CompletableFuture<SystemWorldLocation>()
|
||||
val task = Task { flyShip0(client, location, future) }
|
||||
tasks.add(task)
|
||||
return future
|
||||
}
|
||||
|
||||
val server: StarboundServer
|
||||
var objectSpawnTime: Double
|
||||
private set
|
||||
var lastSpawn = 0.0
|
||||
private set
|
||||
|
||||
private constructor(server: StarboundServer, location: Vector3i) : super(location, server.universeClock, server.universe) {
|
||||
this.server = server
|
||||
this.lastSpawn = clock.seconds - GlobalDefaults.systemWorld.objectSpawnCycle
|
||||
objectSpawnTime = random.nextRange(GlobalDefaults.systemWorld.objectSpawnInterval)
|
||||
}
|
||||
|
||||
private constructor(server: StarboundServer, data: JsonData) : super(data.location, server.universeClock, server.universe) {
|
||||
this.server = server
|
||||
objectSpawnTime = data.objectSpawnTime
|
||||
|
||||
for (obj in data.objects) {
|
||||
ServerEntity(obj)
|
||||
}
|
||||
|
||||
this.lastSpawn = data.lastSpawn
|
||||
}
|
||||
|
||||
private suspend fun spawnInitialObjects() {
|
||||
val random = random(staticRandom64("SystemWorldGeneration", location.toString()))
|
||||
|
||||
GlobalDefaults.systemWorld.initialObjectPools.sample(random).ifPresent {
|
||||
for (i in 0 until it.first) {
|
||||
val name = it.second.sample(random).orNull() ?: return@ifPresent
|
||||
val uuid = UUID(random.nextLong(), random.nextLong())
|
||||
val prototype = GlobalDefaults.systemObjects[name] ?: throw NullPointerException("Tried to create $name system world object, but there is no such object in /system_objects.config!")
|
||||
val create = ServerEntity(prototype.create(uuid, name), uuid, randomObjectSpawnPosition(), clock.seconds)
|
||||
create.enterOrbit(UniversePos(location), Vector2d.ZERO, clock.seconds) // orbit center of system
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun spawnObjects() {
|
||||
var diff = GlobalDefaults.systemWorld.objectSpawnCycle.coerceAtMost(clock.seconds - lastSpawn)
|
||||
lastSpawn = clock.seconds - diff
|
||||
|
||||
while (diff > objectSpawnTime) {
|
||||
lastSpawn += objectSpawnTime
|
||||
objectSpawnTime = random.nextRange(GlobalDefaults.systemWorld.objectSpawnInterval)
|
||||
diff = clock.seconds - lastSpawn
|
||||
|
||||
GlobalDefaults.systemWorld.objectSpawnPool.sample(random).ifPresent {
|
||||
val uuid = UUID(random.nextLong(), random.nextLong())
|
||||
val config = GlobalDefaults.systemObjects[it]?.create(uuid, it) ?: throw NullPointerException("Tried to create $it system world object, but there is no such object in /system_objects.config!")
|
||||
val pos = randomObjectSpawnPosition()
|
||||
|
||||
if (clock.seconds > lastSpawn + objectSpawnTime && config.moving) {
|
||||
// if this is not the last object we're spawning, and it's moving, immediately put it in orbit around a planet
|
||||
val targets = universe.children(systemLocation).filter { child ->
|
||||
entities.values.none { it.orbit.map { it.target == child }.orElse(false) }
|
||||
}
|
||||
|
||||
if (targets.isNotEmpty()) {
|
||||
val target = targets.random(random)
|
||||
val targetPosition = planetPosition(target)
|
||||
val relativeOrbit = (pos - targetPosition).unitVector * (clusterSize(target) / 2.0 + config.orbitDistance)
|
||||
ServerEntity(config, uuid, targetPosition + relativeOrbit, lastSpawn).enterOrbit(target, targetPosition, lastSpawn)
|
||||
} else {
|
||||
ServerEntity(config, uuid, pos, lastSpawn)
|
||||
}
|
||||
} else {
|
||||
ServerEntity(config, uuid, pos, lastSpawn)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun randomObjectSpawnPosition(): Vector2d {
|
||||
val spawnRanges = ArrayList<Vector2d>()
|
||||
val orbits = universe.children(systemLocation)
|
||||
|
||||
suspend fun addSpawn(inner: UniversePos, outer: UniversePos) {
|
||||
val min = planetOrbitDistance(inner) + clusterSize(inner) / 2.0 + GlobalDefaults.systemWorld.objectSpawnPadding
|
||||
val max = planetOrbitDistance(outer) - clusterSize(outer) / 2.0 - GlobalDefaults.systemWorld.objectSpawnPadding
|
||||
spawnRanges.add(Vector2d(min, max))
|
||||
}
|
||||
|
||||
addSpawn(systemLocation, orbits.first())
|
||||
|
||||
for (i in 1 until orbits.size)
|
||||
addSpawn(orbits[i - 1], orbits[i])
|
||||
|
||||
val outer = orbits.last()
|
||||
val rim = planetOrbitDistance(outer) + clusterSize(outer) / 2.0 + GlobalDefaults.systemWorld.objectSpawnPadding
|
||||
spawnRanges.add(Vector2d(rim, rim + GlobalDefaults.systemWorld.objectSpawnPadding))
|
||||
|
||||
val range = spawnRanges.random(random)
|
||||
val angle = random.nextDouble() * PI * 2.0
|
||||
val mag = range.x + random.nextDouble() * (range.y - range.x)
|
||||
return Vector2d(cos(angle) * mag, sin(angle) * mag)
|
||||
}
|
||||
|
||||
fun toJson(): JsonObject {
|
||||
return Starbound.gson.toJsonTree(JsonData(
|
||||
location = location,
|
||||
lastSpawn = lastSpawn,
|
||||
objectSpawnTime = objectSpawnTime,
|
||||
objects = entities.values.stream().map { it.toJson() }.collect(ImmutableList.toImmutableList()),
|
||||
)) as JsonObject
|
||||
}
|
||||
|
||||
private var ticksWithoutPlayers = 0
|
||||
|
||||
fun shouldClose(): Boolean {
|
||||
return ticksWithoutPlayers > 1800
|
||||
}
|
||||
|
||||
// in original engine, ticking happens at 20 updates per second
|
||||
// Since there is no Lua driven code, we can tick as fast as we want
|
||||
suspend fun tick(delta: Double = Starbound.TIMESTEP) {
|
||||
var next = tasks.poll()
|
||||
|
||||
while (next != null) {
|
||||
next.run()
|
||||
next = tasks.poll()
|
||||
}
|
||||
|
||||
entities.values.forEach { it.tick(delta) }
|
||||
ships.values.forEach { it.tick(delta) }
|
||||
|
||||
entities.values.removeIf {
|
||||
if (it.hasExpired && ships.values.none { ship -> ship.location == it.location }) {
|
||||
val packet = SystemObjectDestroyPacket(it.uuid)
|
||||
|
||||
ships.values.forEach { ship ->
|
||||
ship.client.send(packet)
|
||||
}
|
||||
|
||||
return@removeIf true
|
||||
}
|
||||
|
||||
return@removeIf false
|
||||
}
|
||||
|
||||
// spawnObjects()
|
||||
|
||||
ships.values.forEach { it.sendUpdates() }
|
||||
|
||||
if (ships.isEmpty())
|
||||
ticksWithoutPlayers++
|
||||
else
|
||||
ticksWithoutPlayers = 0
|
||||
}
|
||||
|
||||
inner class ServerShip(val client: ServerConnection, location: SystemWorldLocation) : Ship(client.uuid!!, location) {
|
||||
init {
|
||||
ships[uuid] = this
|
||||
}
|
||||
|
||||
private val netVersions = Object2LongOpenHashMap<UUID>()
|
||||
|
||||
suspend fun tick(delta: Double = Starbound.TIMESTEP) {
|
||||
val orbit = destination as? SystemWorldLocation.Orbit
|
||||
|
||||
// if destination is an orbit we haven't started orbiting yet, update the time
|
||||
if (orbit != null)
|
||||
destination = SystemWorldLocation.Orbit(orbit.position.copy(enterTime = clock.seconds))
|
||||
|
||||
suspend fun nearPlanetOrbit(planet: UniversePos): Orbit {
|
||||
val toShip = planetPosition(planet) - position
|
||||
val angle = toShip.toAngle()
|
||||
return Orbit(planet, 1, clock.seconds, Vector2d(cos(angle), sin(angle)) * (planetSize(planet) / 2.0 + GlobalDefaults.systemWorld.clientShip.orbitDistance))
|
||||
}
|
||||
|
||||
if (location is SystemWorldLocation.Celestial) {
|
||||
if (this.orbit?.target != (location as SystemWorldLocation.Celestial).position)
|
||||
this.orbit = nearPlanetOrbit((location as SystemWorldLocation.Celestial).position)
|
||||
} else if (location == SystemWorldLocation.Transit) {
|
||||
departTimer = (departTimer - delta).coerceAtLeast(0.0)
|
||||
|
||||
if (departTimer > 0.0)
|
||||
return
|
||||
|
||||
if (destination is SystemWorldLocation.Celestial) {
|
||||
if (this.orbit?.target != (destination as SystemWorldLocation.Celestial).position)
|
||||
this.orbit = nearPlanetOrbit((destination as SystemWorldLocation.Celestial).position)
|
||||
} else {
|
||||
this.orbit = null
|
||||
}
|
||||
|
||||
val destination: Vector2d
|
||||
|
||||
if (this.orbit != null) {
|
||||
this.orbit = this.orbit!!.copy(enterTime = clock.seconds)
|
||||
destination = orbitPosition(this.orbit!!)
|
||||
} else {
|
||||
destination = this.destination.resolve(this@ServerSystemWorld) ?: position
|
||||
}
|
||||
|
||||
val toTarget = destination - position
|
||||
// don't overshoot
|
||||
position += toTarget.unitVector * (speed * delta).coerceAtMost(toTarget.length.coerceAtMost(1.0))
|
||||
|
||||
if (destination.distanceSquared(position) <= 0.1) {
|
||||
location = this.destination
|
||||
this.destination = SystemWorldLocation.Transit
|
||||
destinationFuture.complete(location)
|
||||
} else {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if (this.orbit != null) {
|
||||
position = orbitPosition(this.orbit!!)
|
||||
} else {
|
||||
position = location.resolve(this@ServerSystemWorld) ?: position
|
||||
}
|
||||
}
|
||||
|
||||
fun sendUpdates() {
|
||||
val objects = HashMap<UUID, ByteArrayList>()
|
||||
val ships = HashMap<UUID, ByteArrayList>()
|
||||
|
||||
for ((id, ship) in this@ServerSystemWorld.ships) {
|
||||
val version = netVersions.getLong(id)
|
||||
|
||||
if (version == 0L || ship.networkGroup.upstream.hasChangedSince(version)) {
|
||||
val (data, newVersion) = ship.networkGroup.write(version, client.isLegacy)
|
||||
netVersions.put(id, newVersion)
|
||||
ships[id] = data
|
||||
}
|
||||
}
|
||||
|
||||
for ((id, entity) in this@ServerSystemWorld.entities) {
|
||||
val version = netVersions.getLong(id)
|
||||
|
||||
if (version == 0L || entity.networkGroup.upstream.hasChangedSince(version)) {
|
||||
val (data, newVersion) = entity.networkGroup.write(version, client.isLegacy)
|
||||
netVersions.put(id, newVersion)
|
||||
objects[id] = data
|
||||
}
|
||||
}
|
||||
|
||||
client.send(SystemWorldUpdatePacket(objects, ships))
|
||||
}
|
||||
|
||||
private var destinationFuture = CompletableFuture<SystemWorldLocation>()
|
||||
|
||||
fun destination(destination: SystemWorldLocation, future: CompletableFuture<SystemWorldLocation>) {
|
||||
destinationFuture.cancel(false)
|
||||
destinationFuture = future
|
||||
|
||||
if (location is SystemWorldLocation.Celestial || location is SystemWorldLocation.Entity)
|
||||
departTimer = GlobalDefaults.systemWorld.clientShip.departTime
|
||||
else if (destination == SystemWorldLocation.Transit)
|
||||
departTimer = GlobalDefaults.systemWorld.clientShip.spaceDepartTime
|
||||
|
||||
this.destination = destination
|
||||
this.location = SystemWorldLocation.Transit
|
||||
}
|
||||
|
||||
fun writeNetwork(stream: DataOutputStream, isLegacy: Boolean) {
|
||||
stream.writeUUID(uuid)
|
||||
location.write(stream, isLegacy)
|
||||
}
|
||||
|
||||
fun writeNetwork(isLegacy: Boolean): ByteArrayList {
|
||||
val data = FastByteArrayOutputStream()
|
||||
writeNetwork(DataOutputStream(data), isLegacy)
|
||||
return ByteArrayList.wrap(data.array, data.length)
|
||||
}
|
||||
}
|
||||
|
||||
inner class ServerEntity(data: SystemWorldObjectConfig.Data, uuid: UUID, position: Vector2d, spawnTime: Double = 0.0, parameters: JsonObject = JsonObject()) : Entity(data, uuid, position, spawnTime, parameters) {
|
||||
constructor(data: EntityJsonData) : this(
|
||||
GlobalDefaults.systemObjects[data.name]?.create(data.actualUUID, data.name) ?: throw NullPointerException("Tried to create ${data.name} system world object, but there is no such object in /system_objects.config!"),
|
||||
data.actualUUID,
|
||||
data.position,
|
||||
data.spawnTime,
|
||||
data.parameters
|
||||
) {
|
||||
if (data.orbit != null) {
|
||||
orbit = KOptional(data.orbit)
|
||||
}
|
||||
}
|
||||
|
||||
constructor(data: JsonObject) : this(Starbound.gson.fromJson(data, EntityJsonData::class.java))
|
||||
|
||||
init {
|
||||
entities[uuid] = this
|
||||
|
||||
val legacy by lazy { SystemObjectCreatePacket(writeNetwork(true)) }
|
||||
val native by lazy { SystemObjectCreatePacket(writeNetwork(false)) }
|
||||
|
||||
ships.values.forEach {
|
||||
it.client.send(if (it.client.isLegacy) legacy else native)
|
||||
}
|
||||
}
|
||||
|
||||
val location = SystemWorldLocation.Entity(uuid)
|
||||
|
||||
var hasExpired = false
|
||||
private set
|
||||
|
||||
suspend fun tick(delta: Double = Starbound.TIMESTEP) {
|
||||
if (!data.permanent && spawnTime > 0.0 && clock.seconds > spawnTime + data.lifeTime)
|
||||
hasExpired = true
|
||||
|
||||
val orbit = orbit.orNull()
|
||||
|
||||
if (orbit != null) {
|
||||
position = orbitPosition(orbit)
|
||||
} else if (data.permanent || !data.moving) {
|
||||
// permanent locations always have a solar orbit
|
||||
enterOrbit(systemLocation, Vector2d.ZERO, clock.seconds)
|
||||
} else if (approach != null) {
|
||||
if (ships.values.any { it.location == location })
|
||||
return
|
||||
|
||||
if (approach!!.isPlanet) {
|
||||
val approach = planetPosition(approach!!)
|
||||
val toApproach = approach - position
|
||||
|
||||
position += toApproach.unitVector * data.speed * delta
|
||||
|
||||
if ((approach - position).length < planetSize(this.approach!!) + data.orbitDistance)
|
||||
enterOrbit(this.approach!!, approach, clock.seconds)
|
||||
} else {
|
||||
enterOrbit(approach!!, Vector2d.ZERO, clock.seconds)
|
||||
}
|
||||
} else {
|
||||
val planets = universe.children(systemLocation).filter { child ->
|
||||
entities.values.none { it.orbit.map { it.target == child }.orElse(false) }
|
||||
}
|
||||
|
||||
if (planets.isNotEmpty())
|
||||
approach = planets.random(random)
|
||||
}
|
||||
}
|
||||
|
||||
fun writeNetwork(stream: DataOutputStream, isLegacy: Boolean) {
|
||||
stream.writeUUID(uuid)
|
||||
stream.writeBinaryString(data.name)
|
||||
stream.writeStruct2d(position, isLegacy)
|
||||
stream.writeJsonObject(parameters)
|
||||
}
|
||||
|
||||
fun writeNetwork(isLegacy: Boolean): ByteArrayList {
|
||||
val data = FastByteArrayOutputStream()
|
||||
writeNetwork(DataOutputStream(data), isLegacy)
|
||||
return ByteArrayList.wrap(data.array, data.length)
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
private val LOGGER = LogManager.getLogger()
|
||||
|
||||
suspend fun create(server: StarboundServer, location: Vector3i): ServerSystemWorld {
|
||||
LOGGER.info("Creating new System World at $location")
|
||||
val world = ServerSystemWorld(server, location)
|
||||
world.spawnInitialObjects()
|
||||
world.spawnObjects()
|
||||
return world
|
||||
}
|
||||
|
||||
suspend fun load(server: StarboundServer, data: JsonElement): ServerSystemWorld {
|
||||
val load = Starbound.gson.fromJson(data, JsonData::class.java)
|
||||
LOGGER.info("Loading System World at ${load.location}")
|
||||
val world = ServerSystemWorld(server, load)
|
||||
world.spawnObjects()
|
||||
return world
|
||||
}
|
||||
}
|
||||
}
|
@ -21,6 +21,8 @@ import ru.dbotthepony.kstarbound.defs.world.CelestialNames
|
||||
import ru.dbotthepony.kstarbound.defs.world.CelestialParameters
|
||||
import ru.dbotthepony.kstarbound.fromJson
|
||||
import ru.dbotthepony.kstarbound.io.BTreeDB5
|
||||
import ru.dbotthepony.kstarbound.util.random.nextRange
|
||||
import ru.dbotthepony.kstarbound.util.random.random
|
||||
import ru.dbotthepony.kstarbound.util.random.staticRandom64
|
||||
import ru.dbotthepony.kstarbound.world.Universe
|
||||
import ru.dbotthepony.kstarbound.world.UniversePos
|
||||
@ -94,7 +96,7 @@ class ServerUniverse private constructor(marker: Nothing?) : Universe(), Closeab
|
||||
return listOf()
|
||||
}
|
||||
|
||||
override suspend fun scanSystems(region: AABBi, includedTypes: Set<String>?): List<UniversePos> {
|
||||
override suspend fun findSystems(region: AABBi, includedTypes: Set<String>?): List<UniversePos> {
|
||||
val copy = if (includedTypes != null) ObjectOpenHashSet(includedTypes) else null
|
||||
val futures = ArrayList<CompletableFuture<List<UniversePos>>>()
|
||||
|
||||
@ -126,7 +128,64 @@ class ServerUniverse private constructor(marker: Nothing?) : Universe(), Closeab
|
||||
return futures.stream().flatMap { it.get().stream() }.toList()
|
||||
}
|
||||
|
||||
override suspend fun scanConstellationLines(region: AABBi): List<Pair<Vector2i, Vector2i>> {
|
||||
override suspend fun <T> scanSystems(region: AABBi, callback: suspend (UniversePos) -> KOptional<T>): KOptional<T> {
|
||||
for (pos in chunkPositions(region)) {
|
||||
val chunk = getChunk(pos) ?: continue
|
||||
|
||||
for (system in chunk.systems.keys) {
|
||||
val result = callback(UniversePos(system))
|
||||
|
||||
if (result.isPresent) {
|
||||
return result
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return KOptional()
|
||||
}
|
||||
|
||||
suspend fun findRandomWorld(tries: Int, range: Int, seed: Long? = null, visitAll: Boolean = false, predicate: suspend (UniversePos) -> Boolean = { true }): UniversePos? {
|
||||
require(tries > 0) { "Non-positive amount of tries: $tries" }
|
||||
require(range > 0) { "Non-positive range: $range" }
|
||||
|
||||
val random = random(seed ?: System.nanoTime())
|
||||
val rect = AABBi(Vector2i(-range, -range), Vector2i(range, range))
|
||||
|
||||
for (i in 0 until tries) {
|
||||
val x = random.nextRange(baseInformation.xyCoordRange)
|
||||
val y = random.nextRange(baseInformation.xyCoordRange)
|
||||
|
||||
val result = scanSystems<UniversePos>(rect + Vector2i(x, y)) {
|
||||
val children = children(it)
|
||||
|
||||
if (children.isEmpty())
|
||||
return@scanSystems KOptional()
|
||||
|
||||
if (visitAll) {
|
||||
for (world in children)
|
||||
if (predicate(world))
|
||||
return@scanSystems KOptional(world)
|
||||
} else {
|
||||
// This sucks, 50% of the time will try and return satellite, not really
|
||||
// balanced probability wise
|
||||
val world = children.random(random)
|
||||
|
||||
if (predicate(world))
|
||||
return@scanSystems KOptional(world)
|
||||
}
|
||||
|
||||
KOptional()
|
||||
}
|
||||
|
||||
if (result.isPresent) {
|
||||
return result.value
|
||||
}
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
override suspend fun scanConstellationLines(region: AABBi, aggressive: Boolean): List<Pair<Vector2i, Vector2i>> {
|
||||
val futures = ArrayList<CompletableFuture<List<Pair<Vector2i, Vector2i>>>>()
|
||||
|
||||
for (pos in chunkPositions(region)) {
|
||||
|
@ -1,5 +1,6 @@
|
||||
package ru.dbotthepony.kstarbound.server.world
|
||||
|
||||
import com.google.gson.JsonElement
|
||||
import com.google.gson.JsonObject
|
||||
import it.unimi.dsi.fastutil.ints.IntArraySet
|
||||
import it.unimi.dsi.fastutil.longs.Long2ObjectFunction
|
||||
@ -13,6 +14,7 @@ import ru.dbotthepony.kommons.vector.Vector2d
|
||||
import ru.dbotthepony.kommons.vector.Vector2i
|
||||
import ru.dbotthepony.kstarbound.Starbound
|
||||
import ru.dbotthepony.kstarbound.defs.WarpAction
|
||||
import ru.dbotthepony.kstarbound.defs.WarpAlias
|
||||
import ru.dbotthepony.kstarbound.defs.WorldID
|
||||
import ru.dbotthepony.kstarbound.defs.tile.TileDamage
|
||||
import ru.dbotthepony.kstarbound.defs.tile.TileDamageResult
|
||||
@ -24,6 +26,7 @@ import ru.dbotthepony.kstarbound.json.jsonArrayOf
|
||||
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.network.packets.clientbound.UpdateWorldPropertiesPacket
|
||||
import ru.dbotthepony.kstarbound.server.StarboundServer
|
||||
import ru.dbotthepony.kstarbound.server.ServerConnection
|
||||
import ru.dbotthepony.kstarbound.util.AssetPathStack
|
||||
@ -295,6 +298,11 @@ class ServerWorld private constructor(
|
||||
}
|
||||
}
|
||||
|
||||
override fun setProperty0(key: String, value: JsonElement) {
|
||||
super.setProperty0(key, value)
|
||||
broadcast(UpdateWorldPropertiesPacket(JsonObject().apply { add(key, value) }))
|
||||
}
|
||||
|
||||
override fun chunkFactory(pos: ChunkPos): ServerChunk {
|
||||
return ServerChunk(this, pos)
|
||||
}
|
||||
@ -521,6 +529,11 @@ class ServerWorld private constructor(
|
||||
world.respawnInWorld = meta.respawnInWorld
|
||||
world.adjustPlayerSpawn = meta.adjustPlayerStart
|
||||
world.centralStructure = meta.centralStructure
|
||||
|
||||
for ((k, v) in meta.worldProperties.entrySet()) {
|
||||
world.setProperty(k, v)
|
||||
}
|
||||
|
||||
world.protectedDungeonIDs.addAll(meta.protectedDungeonIds)
|
||||
world
|
||||
}
|
||||
|
@ -49,7 +49,10 @@ class ServerWorldTracker(val world: ServerWorld, val client: ServerConnection, p
|
||||
client.worldID = world.worldID
|
||||
}
|
||||
|
||||
var skyVersion = 0L
|
||||
private var skyVersion = 0L
|
||||
// this is required because of dumb shit regarding flash time
|
||||
// if we network sky state on each tick then it will guarantee epilepsy attack
|
||||
private var skyUpdateWaitTicks = 0
|
||||
|
||||
private val isRemoved = AtomicBoolean()
|
||||
private var isActuallyRemoved = false
|
||||
@ -99,7 +102,7 @@ class ServerWorldTracker(val world: ServerWorld, val client: ServerConnection, p
|
||||
dungeonGravity = mapOf(),
|
||||
dungeonBreathable = mapOf(),
|
||||
protectedDungeonIDs = world.protectedDungeonIDs,
|
||||
worldProperties = world.properties.deepCopy(),
|
||||
worldProperties = world.copyProperties(),
|
||||
connectionID = client.connectionID,
|
||||
localInterpolationMode = false,
|
||||
))
|
||||
@ -143,9 +146,12 @@ class ServerWorldTracker(val world: ServerWorld, val client: ServerConnection, p
|
||||
}
|
||||
|
||||
run {
|
||||
val (data, version) = world.sky.networkedGroup.write(skyVersion, isLegacy = client.isLegacy)
|
||||
skyVersion = version
|
||||
send(EnvironmentUpdatePacket(data, ByteArrayList()))
|
||||
if (skyUpdateWaitTicks++ >= 4) {
|
||||
val (data, version) = world.sky.networkedGroup.write(skyVersion, isLegacy = client.isLegacy)
|
||||
skyVersion = version
|
||||
send(EnvironmentUpdatePacket(data, ByteArrayList()))
|
||||
skyUpdateWaitTicks = 0
|
||||
}
|
||||
}
|
||||
|
||||
run {
|
||||
|
@ -10,23 +10,26 @@ import com.google.gson.stream.JsonReader
|
||||
import com.google.gson.stream.JsonWriter
|
||||
import it.unimi.dsi.fastutil.ints.Int2ObjectMap
|
||||
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap
|
||||
import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap
|
||||
import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet
|
||||
import ru.dbotthepony.kommons.gson.consumeNull
|
||||
import ru.dbotthepony.kommons.gson.get
|
||||
import ru.dbotthepony.kommons.gson.getArray
|
||||
import ru.dbotthepony.kommons.util.KOptional
|
||||
import ru.dbotthepony.kommons.vector.Vector2i
|
||||
import ru.dbotthepony.kommons.vector.Vector3i
|
||||
import ru.dbotthepony.kstarbound.defs.world.CelestialParameters
|
||||
import ru.dbotthepony.kstarbound.defs.world.CelestialPlanet
|
||||
import ru.dbotthepony.kstarbound.json.builder.JsonFactory
|
||||
import ru.dbotthepony.kstarbound.json.pairAdapter
|
||||
import ru.dbotthepony.kstarbound.json.pairListAdapter
|
||||
import ru.dbotthepony.kstarbound.network.packets.clientbound.CelestialResponsePacket
|
||||
import ru.dbotthepony.kstarbound.world.UniversePos
|
||||
import java.util.HashMap
|
||||
import java.util.stream.Collectors
|
||||
|
||||
class UniverseChunk(var chunkPos: Vector2i = Vector2i.ZERO) {
|
||||
data class System(val parameters: CelestialParameters, val planets: Int2ObjectMap<CelestialPlanet>)
|
||||
data class System(val parameters: CelestialParameters, val planets: Int2ObjectMap<Planet>)
|
||||
|
||||
@JsonFactory
|
||||
data class Planet(val parameters: CelestialParameters, val satellites: Int2ObjectMap<CelestialParameters>)
|
||||
|
||||
val systems = HashMap<Vector3i, System>()
|
||||
val constellations = ObjectOpenHashSet<Pair<Vector2i, Vector2i>>()
|
||||
@ -44,6 +47,15 @@ class UniverseChunk(var chunkPos: Vector2i = Vector2i.ZERO) {
|
||||
throw RuntimeException("unreachable code")
|
||||
}
|
||||
|
||||
fun toNetwork(): CelestialResponsePacket.ChunkData {
|
||||
return CelestialResponsePacket.ChunkData(
|
||||
chunkPos,
|
||||
ArrayList(constellations),
|
||||
systems.entries.associate { it.key to it.value.parameters },
|
||||
systems.entries.associate { it.key to it.value.planets.int2ObjectEntrySet().associate { it.intKey to CelestialResponsePacket.PlanetData(it.value.parameters, HashMap(it.value.satellites)) } },
|
||||
)
|
||||
}
|
||||
|
||||
class Adapter(gson: Gson) : TypeAdapter<UniverseChunk>() {
|
||||
private val vectors = gson.getAdapter(Vector2i::class.java)
|
||||
private val vectors3 = gson.getAdapter(Vector3i::class.java)
|
||||
@ -164,7 +176,7 @@ class UniverseChunk(var chunkPos: Vector2i = Vector2i.ZERO) {
|
||||
val orbit = planetPair[0].asInt
|
||||
val params = planetPair[1] as JsonObject
|
||||
|
||||
val planet = CelestialPlanet(this.params.fromJsonTree(params["parameters"]), Int2ObjectOpenHashMap())
|
||||
val planet = Planet(this.params.fromJsonTree(params["parameters"]), Int2ObjectOpenHashMap())
|
||||
val satellitesPairs = params["satellites"] as JsonArray
|
||||
|
||||
for (satellitePair in satellitesPairs) {
|
||||
|
@ -18,8 +18,6 @@ import ru.dbotthepony.kommons.vector.Vector2i
|
||||
import ru.dbotthepony.kommons.vector.Vector3i
|
||||
import ru.dbotthepony.kstarbound.Starbound
|
||||
import ru.dbotthepony.kstarbound.defs.world.CelestialParameters
|
||||
import ru.dbotthepony.kstarbound.defs.world.CelestialPlanet
|
||||
import ru.dbotthepony.kstarbound.defs.JsonDriven
|
||||
import ru.dbotthepony.kstarbound.io.BTreeDB5
|
||||
import ru.dbotthepony.kstarbound.json.mergeJson
|
||||
import ru.dbotthepony.kstarbound.json.readJsonElement
|
||||
@ -204,7 +202,7 @@ class NativeUniverseSource(private val db: BTreeDB6?, private val universe: Serv
|
||||
systemParams.parameters["constellationCapable"] = system.constellationCapable
|
||||
}
|
||||
|
||||
val planets = Int2ObjectArrayMap<CelestialPlanet>()
|
||||
val planets = Int2ObjectArrayMap<UniverseChunk.Planet>()
|
||||
|
||||
for (planetOrbitIndex in 1 .. universe.baseInformation.planetOrbitalLevels) {
|
||||
// this looks dumb at first, but then it makes sense
|
||||
@ -266,7 +264,7 @@ class NativeUniverseSource(private val db: BTreeDB6?, private val universe: Serv
|
||||
}
|
||||
}
|
||||
|
||||
planets[planetOrbitIndex] = CelestialPlanet(planetParams, satellites)
|
||||
planets[planetOrbitIndex] = UniverseChunk.Planet(planetParams, satellites)
|
||||
}
|
||||
|
||||
return UniverseChunk.System(systemParams, planets)
|
||||
|
@ -21,8 +21,8 @@ val JsonElement.coalesceNull: JsonElement?
|
||||
|
||||
fun UUID.toStarboundString(): String {
|
||||
val builder = StringBuilder(32)
|
||||
val a = mostSignificantBits.toString(16)
|
||||
val b = mostSignificantBits.toString(16)
|
||||
val a = java.lang.Long.toUnsignedString(mostSignificantBits, 16)
|
||||
val b = java.lang.Long.toUnsignedString(leastSignificantBits, 16)
|
||||
|
||||
for (i in a.length until 16)
|
||||
builder.append("0")
|
||||
@ -37,6 +37,17 @@ fun UUID.toStarboundString(): String {
|
||||
return builder.toString()
|
||||
}
|
||||
|
||||
fun uuidFromStarboundString(value: String): UUID {
|
||||
if (value.length != 32) {
|
||||
throw IllegalArgumentException("Not a UUID string: $value")
|
||||
}
|
||||
|
||||
val a = value.substring(0, 16)
|
||||
val b = value.substring(16)
|
||||
|
||||
return UUID(a.toLong(16), b.toLong(16))
|
||||
}
|
||||
|
||||
fun paddedNumber(number: Int, digits: Int): String {
|
||||
val str = number.toString()
|
||||
|
||||
|
@ -3,15 +3,15 @@ package ru.dbotthepony.kstarbound.util.random
|
||||
import ru.dbotthepony.kstarbound.getValue
|
||||
import java.util.random.RandomGenerator
|
||||
|
||||
// multiply with carry random number generator, as used by original Starbound
|
||||
// Game's code SHOULD NOT be tied to this generator, random() global function should be used
|
||||
// What interesting though, is the size of cycle - 256 (8092 bits). Others use much bigger number - 4096
|
||||
class MWCRandom(seed: Long = System.nanoTime(), cycle: Int = 256, windupIterations: Int = 32) : RandomGenerator {
|
||||
private val data = IntArray(cycle)
|
||||
private var carry = 0
|
||||
// MWC Random (multiply with carry), exact replica from original code
|
||||
// (for purpose of giving exactly the same results for same seed provided)
|
||||
@OptIn(ExperimentalUnsignedTypes::class)
|
||||
class MWCRandom(seed: ULong = System.nanoTime().toULong(), cycle: Int = 256, windupIterations: Int = 32) : RandomGenerator {
|
||||
private val data = UIntArray(cycle)
|
||||
private var carry = 0u
|
||||
private var dataIndex = 0
|
||||
|
||||
var seed: Long = seed
|
||||
var seed: ULong = seed
|
||||
private set
|
||||
|
||||
init {
|
||||
@ -22,49 +22,72 @@ class MWCRandom(seed: Long = System.nanoTime(), cycle: Int = 256, windupIteratio
|
||||
/**
|
||||
* re-initialize this MWC generator
|
||||
*/
|
||||
fun init(seed: Long, windupIterations: Int = 0) {
|
||||
fun init(seed: ULong, windupIterations: Int = 0) {
|
||||
this.seed = seed
|
||||
carry = (seed % MAGIC).toInt()
|
||||
carry = (seed % MAGIC).toUInt()
|
||||
|
||||
data[0] = seed.toInt()
|
||||
data[1] = seed.ushr(32).toInt()
|
||||
data[0] = seed.toUInt()
|
||||
data[1] = seed.shr(32).toUInt()
|
||||
|
||||
for (i in 2 until data.size) {
|
||||
data[i] = 69069 * data[i - 2] + 362437
|
||||
data[i] = 69069u * data[i - 2] + 362437u
|
||||
}
|
||||
|
||||
dataIndex = data.size - 1
|
||||
|
||||
// initial windup
|
||||
for (i in 0 until windupIterations) {
|
||||
nextLong()
|
||||
nextInt()
|
||||
}
|
||||
}
|
||||
|
||||
fun addEntropy(seed: Long = System.nanoTime()) {
|
||||
fun addEntropy(seed: ULong = System.nanoTime().toULong()) {
|
||||
// Same algo as init, but bitwise xor with existing data
|
||||
carry = ((carry.toLong().xor(seed)) % MAGIC).toInt()
|
||||
carry = ((carry.toULong().xor(seed)) % MAGIC).toUInt()
|
||||
|
||||
data[0] = data[0].xor(seed.toInt())
|
||||
data[1] = data[1].xor(seed.ushr(32).xor(seed).toInt())
|
||||
data[0] = data[0].xor(seed.toUInt())
|
||||
data[1] = data[1].xor(seed.shr(32).xor(seed).toUInt())
|
||||
|
||||
for (i in 2 until data.size) {
|
||||
data[i] = data[i].xor(69069 * data[i - 2] + 362437)
|
||||
data[i] = data[i].xor(69069u * data[i - 2] + 362437u)
|
||||
}
|
||||
}
|
||||
|
||||
override fun nextInt(): Int {
|
||||
dataIndex = (dataIndex + 1) % data.size
|
||||
val t = MAGIC.toULong() * data[dataIndex] + carry
|
||||
//val t = MAGIC.toLong() * data[dataIndex].toLong() + carry.toLong()
|
||||
|
||||
carry = t.shr(32).toUInt()
|
||||
data[dataIndex] = t.toUInt()
|
||||
|
||||
return t.toInt()
|
||||
}
|
||||
|
||||
override fun nextLong(): Long {
|
||||
dataIndex = (dataIndex + 1) % data.size
|
||||
val t = MAGIC.toLong() * data[dataIndex].toLong() + carry.toLong()
|
||||
val a = nextInt().toLong() and 0xFFFFFFFFL
|
||||
val b = nextInt().toLong() and 0xFFFFFFFFL
|
||||
return a.shl(32) or b
|
||||
}
|
||||
|
||||
carry = t.ushr(32).toInt()
|
||||
data[dataIndex] = t.toInt()
|
||||
override fun nextFloat(): Float {
|
||||
return (nextInt() and 0x7fffffff) / 2.14748365E9f
|
||||
}
|
||||
|
||||
return t
|
||||
override fun nextDouble(): Double {
|
||||
return (nextLong() and 0x7fffffffffffffffL) / 9.223372036854776E18
|
||||
}
|
||||
|
||||
override fun nextDouble(origin: Double, bound: Double): Double {
|
||||
return nextDouble() * (bound - origin) + origin
|
||||
}
|
||||
|
||||
override fun nextFloat(origin: Float, bound: Float): Float {
|
||||
return nextFloat() * (bound - origin) + origin
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val MAGIC = 809430660
|
||||
const val MAGIC = 809430660u
|
||||
|
||||
val GLOBAL: MWCRandom by ThreadLocal.withInitial { MWCRandom() }
|
||||
}
|
||||
|
@ -21,7 +21,7 @@ import kotlin.math.sqrt
|
||||
* Replacing generator returned here will affect all random generation code.
|
||||
*/
|
||||
fun random(seed: Long = System.nanoTime()): RandomGenerator {
|
||||
return MWCRandom(seed)
|
||||
return MWCRandom(seed.toULong())
|
||||
}
|
||||
|
||||
private fun toBytes(accept: ByteConsumer, value: Short) {
|
||||
|
@ -65,6 +65,9 @@ abstract class CoordinateMapper {
|
||||
open fun isValidCellIndex(value: Int): Boolean = inBoundsCell(value)
|
||||
open fun isValidChunkIndex(value: Int): Boolean = inBoundsChunk(value)
|
||||
|
||||
abstract fun diff(a: Int, b: Int): Int
|
||||
abstract fun diff(a: Double, b: Double): Double
|
||||
|
||||
class Wrapper(private val cells: Int) : CoordinateMapper() {
|
||||
override val chunks = divideUp(cells, CHUNK_SIZE)
|
||||
private val cellsD = cells.toDouble()
|
||||
@ -88,6 +91,36 @@ abstract class CoordinateMapper {
|
||||
override fun isValidCellIndex(value: Int) = true
|
||||
override fun isValidChunkIndex(value: Int) = true
|
||||
|
||||
@Suppress("NAME_SHADOWING")
|
||||
override fun diff(a: Int, b: Int): Int {
|
||||
val a = cell(a)
|
||||
val b = cell(b)
|
||||
|
||||
var diff = a - b
|
||||
|
||||
if (diff > cells / 2)
|
||||
diff -= cells
|
||||
else if (diff < -cells / 2)
|
||||
diff += cells
|
||||
|
||||
return diff
|
||||
}
|
||||
|
||||
@Suppress("NAME_SHADOWING")
|
||||
override fun diff(a: Double, b: Double): Double {
|
||||
val a = cell(a)
|
||||
val b = cell(b)
|
||||
|
||||
var diff = a - b
|
||||
|
||||
if (diff > cells / 2)
|
||||
diff -= cells
|
||||
else if (diff < -cells / 2)
|
||||
diff += cells
|
||||
|
||||
return diff
|
||||
}
|
||||
|
||||
override fun chunkFromCell(value: Int): Int {
|
||||
return chunk(value shr CHUNK_SIZE_BITS)
|
||||
}
|
||||
@ -186,6 +219,14 @@ abstract class CoordinateMapper {
|
||||
|
||||
private var cellsEdgeFloat = cellsF
|
||||
|
||||
override fun diff(a: Int, b: Int): Int {
|
||||
return (a - b).coerceIn(0, cells)
|
||||
}
|
||||
|
||||
override fun diff(a: Double, b: Double): Double {
|
||||
return (a - b).coerceIn(0.0, cellsD)
|
||||
}
|
||||
|
||||
init {
|
||||
var power = -64f
|
||||
|
||||
|
@ -34,6 +34,8 @@ class Sky() {
|
||||
private val skyParametersNetState = networkedGroup.upstream.add(networkedJson(SkyParameters()))
|
||||
|
||||
var skyType by networkedGroup.upstream.add(networkedEnumStupid(SkyType.ORBITAL))
|
||||
private set
|
||||
|
||||
var time by networkedGroup.upstream.add(networkedDouble())
|
||||
private set
|
||||
var flyingType by networkedGroup.upstream.add(networkedEnum(FlyingType.NONE))
|
||||
@ -42,12 +44,12 @@ class Sky() {
|
||||
private set
|
||||
var startInWarp by networkedGroup.upstream.add(networkedBoolean())
|
||||
private set
|
||||
var warpPhase by networkedGroup.upstream.add(networkedData(WarpPhase.MAINTAIN, VarIntValueCodec.map({ WarpPhase.entries[this - 1] }, { ordinal - 1 })))
|
||||
private set
|
||||
var worldMoveOffset by networkedGroup.upstream.add(networkedVec2f())
|
||||
private set
|
||||
var starMoveOffset by networkedGroup.upstream.add(networkedVec2f())
|
||||
private set
|
||||
var warpPhase by networkedGroup.upstream.add(networkedData(WarpPhase.MAINTAIN, VarIntValueCodec.map({ WarpPhase.entries[this - 1] }, { ordinal - 1 })))
|
||||
private set
|
||||
var flyingTimer by networkedGroup.upstream.add(networkedFloat())
|
||||
private set
|
||||
|
||||
@ -61,6 +63,8 @@ class Sky() {
|
||||
var pathOffset = Vector2d.ZERO
|
||||
private set
|
||||
|
||||
var worldRotation: Double = 0.0
|
||||
private set
|
||||
var starRotation: Double = 0.0
|
||||
private set
|
||||
var pathRotation: Double = 0.0
|
||||
@ -98,14 +102,19 @@ class Sky() {
|
||||
fun startFlying(enterHyperspace: Boolean, startInWarp: Boolean = false) {
|
||||
if (startInWarp)
|
||||
flyingType = FlyingType.WARP
|
||||
else
|
||||
else if (flyingType == FlyingType.NONE)
|
||||
flyingType = FlyingType.DISEMBARKING
|
||||
|
||||
flyingTimer = 0.0
|
||||
// flyingTimer = 0.0
|
||||
this.enterHyperspace = enterHyperspace
|
||||
this.startInWarp = startInWarp
|
||||
}
|
||||
|
||||
fun stopFlyingAt(destination: SkyParameters) {
|
||||
this.destination = destination
|
||||
skyType = SkyType.ORBITAL
|
||||
}
|
||||
|
||||
private var lastFlyingType = FlyingType.NONE
|
||||
private var lastWarpPhase = WarpPhase.MAINTAIN
|
||||
private var sentSFX = false
|
||||
@ -141,11 +150,32 @@ class Sky() {
|
||||
|
||||
when (warpPhase) {
|
||||
WarpPhase.SLOWING_DOWN -> {
|
||||
skyType = SkyType.ORBITAL
|
||||
//flashTimer = GlobalDefaults.sky.flashTimer
|
||||
sentSFX = false
|
||||
|
||||
val origin = if (skyType == SkyType.SPACE) GlobalDefaults.sky.spaceArrivalOrigin else GlobalDefaults.sky.arrivalOrigin
|
||||
val path = if (skyType == SkyType.SPACE) GlobalDefaults.sky.spaceArrivalPath else GlobalDefaults.sky.arrivalPath
|
||||
|
||||
pathOffset = origin.offset
|
||||
pathRotation = origin.rotationRad
|
||||
|
||||
var exitDistance = GlobalDefaults.sky.flyMaxVelocity / 2.0 * slowdownTime
|
||||
worldMoveOffset = Vector2d(x = exitDistance)
|
||||
worldOffset = worldMoveOffset
|
||||
|
||||
exitDistance *= GlobalDefaults.sky.starVelocityFactor
|
||||
starMoveOffset = Vector2d(x = exitDistance)
|
||||
starOffset = starMoveOffset
|
||||
|
||||
worldRotation = 0.0
|
||||
starRotation = 0.0
|
||||
flyingTimer = 0.0
|
||||
}
|
||||
|
||||
WarpPhase.MAINTAIN -> {
|
||||
flashTimer = GlobalDefaults.sky.flashTimer
|
||||
//flashTimer = GlobalDefaults.sky.flashTimer
|
||||
|
||||
skyType = SkyType.WARP
|
||||
sentSFX = false
|
||||
}
|
||||
@ -157,6 +187,7 @@ class Sky() {
|
||||
}
|
||||
|
||||
lastFlyingType = flyingType
|
||||
lastWarpPhase = warpPhase
|
||||
}
|
||||
|
||||
|
||||
|
292
src/main/kotlin/ru/dbotthepony/kstarbound/world/SystemWorld.kt
Normal file
292
src/main/kotlin/ru/dbotthepony/kstarbound/world/SystemWorld.kt
Normal file
@ -0,0 +1,292 @@
|
||||
package ru.dbotthepony.kstarbound.world
|
||||
|
||||
import com.google.gson.JsonObject
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import ru.dbotthepony.kommons.io.koptional
|
||||
import ru.dbotthepony.kommons.io.writeBinaryString
|
||||
import ru.dbotthepony.kommons.io.writeUUID
|
||||
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.Vector3i
|
||||
import ru.dbotthepony.kstarbound.GlobalDefaults
|
||||
import ru.dbotthepony.kstarbound.Starbound
|
||||
import ru.dbotthepony.kstarbound.defs.world.FloatingDungeonWorldParameters
|
||||
import ru.dbotthepony.kstarbound.defs.world.SystemWorldObjectConfig
|
||||
import ru.dbotthepony.kstarbound.io.readDouble
|
||||
import ru.dbotthepony.kstarbound.io.readVector2d
|
||||
import ru.dbotthepony.kstarbound.io.writeDouble
|
||||
import ru.dbotthepony.kstarbound.io.writeStruct2d
|
||||
import ru.dbotthepony.kstarbound.json.builder.JsonFactory
|
||||
import ru.dbotthepony.kstarbound.json.writeJsonObject
|
||||
import ru.dbotthepony.kstarbound.math.Interpolator
|
||||
import ru.dbotthepony.kstarbound.network.syncher.MasterElement
|
||||
import ru.dbotthepony.kstarbound.network.syncher.NetworkedGroup
|
||||
import ru.dbotthepony.kstarbound.network.syncher.legacyCodec
|
||||
import ru.dbotthepony.kstarbound.network.syncher.nativeCodec
|
||||
import ru.dbotthepony.kstarbound.network.syncher.networkedData
|
||||
import ru.dbotthepony.kstarbound.network.syncher.networkedFloat
|
||||
import ru.dbotthepony.kstarbound.util.Clock
|
||||
import ru.dbotthepony.kstarbound.util.random.MWCRandom
|
||||
import ru.dbotthepony.kstarbound.util.random.nextRange
|
||||
import ru.dbotthepony.kstarbound.util.random.random
|
||||
import ru.dbotthepony.kstarbound.util.random.staticRandom64
|
||||
import ru.dbotthepony.kstarbound.util.toStarboundString
|
||||
import ru.dbotthepony.kstarbound.util.uuidFromStarboundString
|
||||
import java.io.DataInputStream
|
||||
import java.io.DataOutputStream
|
||||
import java.util.UUID
|
||||
import kotlin.math.PI
|
||||
import kotlin.math.cos
|
||||
import kotlin.math.sin
|
||||
import kotlin.math.sqrt
|
||||
|
||||
abstract class SystemWorld(val location: Vector3i, val clock: Clock, val universe: Universe) {
|
||||
val random = random()
|
||||
abstract val entities: Map<UUID, Entity>
|
||||
abstract val ships: Map<UUID, Ship>
|
||||
val systemLocation: UniversePos = UniversePos(location)
|
||||
|
||||
suspend fun planetOrbitDistance(coordinate: UniversePos): Double {
|
||||
if (coordinate.isSystem)
|
||||
return 0.0
|
||||
|
||||
val random = MWCRandom(compatCoordinateSeed(coordinate, "PlanetOrbitDistance").toULong(), cycle = 256, windupIterations = 32)
|
||||
var distance = planetSize(coordinate.parent()) / 2.0
|
||||
|
||||
for (i in 0 until coordinate.orbitNumber) {
|
||||
if (i > 0) {
|
||||
distance += clusterSize(coordinate.parent().child(i))
|
||||
}
|
||||
|
||||
if (coordinate.isPlanet)
|
||||
distance += random.nextFloat(GlobalDefaults.systemWorld.planetaryOrbitPadding.x.toFloat(), GlobalDefaults.systemWorld.planetaryOrbitPadding.y.toFloat())
|
||||
else if (coordinate.isSatellite)
|
||||
distance += random.nextFloat(GlobalDefaults.systemWorld.satelliteOrbitPadding.x.toFloat(), GlobalDefaults.systemWorld.satelliteOrbitPadding.y.toFloat())
|
||||
}
|
||||
|
||||
distance += clusterSize(coordinate) / 2.0
|
||||
return distance
|
||||
}
|
||||
|
||||
suspend fun clusterSize(coordinate: UniversePos): Double {
|
||||
if (coordinate.isPlanet && universe.children(coordinate.parent()).any { it.orbitNumber == coordinate.orbitNumber }) {
|
||||
val child = universe.children(coordinate).sorted()
|
||||
|
||||
if (child.isNotEmpty()) {
|
||||
val outer = coordinate.child(child.last().orbitNumber)
|
||||
return planetOrbitDistance(outer) * 2.0 + planetSize(outer)
|
||||
} else {
|
||||
return planetSize(coordinate)
|
||||
}
|
||||
} else {
|
||||
return planetSize(coordinate)
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun planetSize(coordinate: UniversePos): Double {
|
||||
if (coordinate.isSystem)
|
||||
return GlobalDefaults.systemWorld.starSize
|
||||
|
||||
if (!universe.children(coordinate.parent()).any { it.orbitNumber == coordinate.orbitNumber })
|
||||
return GlobalDefaults.systemWorld.emptyOrbitSize
|
||||
|
||||
val parameters = universe.parameters(coordinate)
|
||||
|
||||
if (parameters != null) {
|
||||
val visitable = parameters.visitableParameters
|
||||
|
||||
if (visitable != null) {
|
||||
var size = 0.0
|
||||
|
||||
if (visitable is FloatingDungeonWorldParameters) {
|
||||
val getSize = GlobalDefaults.systemWorld.floatingDungeonWorldSizes[visitable.typeName]
|
||||
|
||||
if (getSize != null) {
|
||||
return getSize
|
||||
}
|
||||
}
|
||||
|
||||
for ((planetSize, orbitSize) in GlobalDefaults.systemWorld.planetSizes) {
|
||||
if (visitable.worldSize.x >= planetSize)
|
||||
size = orbitSize
|
||||
else
|
||||
break
|
||||
}
|
||||
|
||||
return size
|
||||
}
|
||||
}
|
||||
|
||||
return GlobalDefaults.systemWorld.unvisitablePlanetSize
|
||||
}
|
||||
|
||||
fun orbitInterval(distance: Double, isSatellite: Boolean): Double {
|
||||
val gravityConstant = if (isSatellite) GlobalDefaults.systemWorld.planetGravitationalConstant else GlobalDefaults.systemWorld.starGravitationalConstant
|
||||
return distance * 2.0 * PI / sqrt(gravityConstant / distance)
|
||||
}
|
||||
|
||||
fun compatCoordinateSeed(coordinate: UniversePos, seedMix: String): Long {
|
||||
// original code is utterly broken here
|
||||
|
||||
// consider the following:
|
||||
// auto satellite = coordinate.isSatelliteBody() ? coordinate.orbitNumber() : 0;
|
||||
// auto planet = coordinate.isSatelliteBody() ? coordinate.parent().orbitNumber() : coordinate.isPlanetaryBody() && coordinate.orbitNumber() || 0;
|
||||
|
||||
// first obvious problem: coordinate.isPlanetaryBody() && coordinate.orbitNumber() || 0
|
||||
// this "coalesces" planet orbit into either 0 or 1
|
||||
// then, we have coordinate.parent().orbitNumber(), which is correct, but only if we are orbiting a satellite
|
||||
|
||||
// TODO: Use correct logic when there are no legacy clients in this system
|
||||
// Correct logic properly randomizes starting planet orbits, and they feel much more natural
|
||||
|
||||
return staticRandom64(coordinate.location.x, coordinate.location.y, coordinate.location.z, if (coordinate.isPlanet) 1 else coordinate.planetOrbit, coordinate.satelliteOrbit, seedMix)
|
||||
}
|
||||
|
||||
suspend fun planetPosition(coordinate: UniversePos): Vector2d {
|
||||
if (coordinate.isSystem)
|
||||
return Vector2d.ZERO
|
||||
|
||||
// this thing must produce EXACT result between legacy client and new server
|
||||
val random = MWCRandom(compatCoordinateSeed(coordinate, "PlanetSystemPosition").toULong(), cycle = 256, windupIterations = 32)
|
||||
val parentPosition = planetPosition(coordinate.parent())
|
||||
val distance = planetOrbitDistance(coordinate)
|
||||
val interval = orbitInterval(distance, coordinate.isSatellite)
|
||||
|
||||
val start = random.nextFloat().toDouble()
|
||||
val offset = (clock.seconds % interval) / interval
|
||||
val direction = if (random.nextFloat() > 0.5f) 1 else -1
|
||||
val angle = (start + direction * offset) * PI * 2.0
|
||||
return parentPosition + Vector2d(cos(angle) * distance, sin(angle) * distance)
|
||||
}
|
||||
|
||||
suspend fun orbitPosition(orbit: Orbit): Vector2d {
|
||||
val targetPosition = if (orbit.target.isPlanet || orbit.target.isSatellite) planetPosition(orbit.target) else Vector2d.ZERO
|
||||
val distance = orbit.enterPosition.length
|
||||
val interval = orbitInterval(distance, false)
|
||||
val timeOffset = ((clock.seconds - orbit.enterTime) % interval) / interval
|
||||
val angle = (orbit.enterPosition * -1).toAngle() + orbit.direction * timeOffset * PI * 2.0
|
||||
return targetPosition + Vector2d(cos(angle) * distance, sin(angle) * distance)
|
||||
}
|
||||
|
||||
fun randomArrivalPosition(): Vector2d {
|
||||
val range = random.nextRange(GlobalDefaults.systemWorld.arrivalRange)
|
||||
val angle = random.nextDouble(0.0, PI * 2.0)
|
||||
return Vector2d(cos(angle), sin(angle)) * range
|
||||
}
|
||||
|
||||
data class Orbit(val target: UniversePos, val direction: Int, val enterTime: Double, val enterPosition: Vector2d) {
|
||||
constructor(stream: DataInputStream, isLegacy: Boolean) : this(
|
||||
UniversePos(stream, isLegacy),
|
||||
if (isLegacy) stream.readInt() else stream.readByte().toInt(),
|
||||
stream.readDouble(),
|
||||
stream.readVector2d(isLegacy)
|
||||
)
|
||||
|
||||
fun write(stream: DataOutputStream, isLegacy: Boolean) {
|
||||
target.write(stream, isLegacy)
|
||||
if (isLegacy) stream.writeInt(direction) else stream.writeByte(direction)
|
||||
stream.writeDouble(enterTime)
|
||||
stream.writeStruct2d(enterPosition, isLegacy)
|
||||
}
|
||||
|
||||
companion object {
|
||||
val CODEC = nativeCodec(::Orbit, Orbit::write).koptional()
|
||||
val LEGACY_CODEC = legacyCodec(::Orbit, Orbit::write).koptional()
|
||||
}
|
||||
}
|
||||
|
||||
@JsonFactory
|
||||
data class EntityJsonData(
|
||||
val name: String,
|
||||
val uuid: String,
|
||||
val parameters: JsonObject = JsonObject(),
|
||||
val spawnTime: Double,
|
||||
val position: Vector2d,
|
||||
val orbit: Orbit? = null,
|
||||
) {
|
||||
val actualUUID = uuidFromStarboundString(uuid)
|
||||
}
|
||||
|
||||
abstract inner class Ship(val uuid: UUID, location: SystemWorldLocation) {
|
||||
var speed = GlobalDefaults.systemWorld.clientShip.speed
|
||||
var departTimer = 0.0
|
||||
|
||||
val networkGroup = MasterElement(NetworkedGroup())
|
||||
|
||||
// systemLocation should not be interpolated
|
||||
// if it's stale it can point to a removed system object
|
||||
var location by networkedData(location, SystemWorldLocation.CODEC, SystemWorldLocation.LEGACY_CODEC).also { networkGroup.upstream.add(it, false) }
|
||||
var destination by networkedData(location, SystemWorldLocation.CODEC, SystemWorldLocation.LEGACY_CODEC).also { networkGroup.upstream.add(it, false) }
|
||||
|
||||
var xPosition by networkedFloat().also { networkGroup.upstream.add(it); it.interpolator = Interpolator.Linear }
|
||||
var yPosition by networkedFloat().also { networkGroup.upstream.add(it); it.interpolator = Interpolator.Linear }
|
||||
|
||||
var orbit: Orbit? = null
|
||||
|
||||
var position: Vector2d
|
||||
get() = Vector2d(xPosition, yPosition)
|
||||
set(value) {
|
||||
xPosition = value.x
|
||||
yPosition = value.y
|
||||
}
|
||||
|
||||
init {
|
||||
networkGroup.upstream.enableInterpolation()
|
||||
}
|
||||
}
|
||||
|
||||
abstract inner class Entity(val data: SystemWorldObjectConfig.Data, val uuid: UUID, position: Vector2d, val spawnTime: Double = 0.0, val parameters: JsonObject = JsonObject()) {
|
||||
constructor(data: EntityJsonData) : this(
|
||||
GlobalDefaults.systemObjects[data.name]?.create(data.actualUUID, data.name) ?: throw NullPointerException("Tried to create ${data.name} system world object, but there is no such object in /system_objects.config!"),
|
||||
data.actualUUID,
|
||||
data.position,
|
||||
data.spawnTime,
|
||||
data.parameters
|
||||
) {
|
||||
if (data.orbit != null) {
|
||||
orbit = KOptional(data.orbit)
|
||||
}
|
||||
}
|
||||
|
||||
constructor(data: JsonObject) : this(Starbound.gson.fromJson(data, EntityJsonData::class.java))
|
||||
|
||||
val networkGroup = MasterElement(NetworkedGroup())
|
||||
|
||||
var xPosition by networkedFloat().also { networkGroup.upstream.add(it); it.interpolator = Interpolator.Linear }
|
||||
var yPosition by networkedFloat().also { networkGroup.upstream.add(it); it.interpolator = Interpolator.Linear }
|
||||
var orbit by networkedData(KOptional(), Orbit.CODEC, Orbit.LEGACY_CODEC).also { networkGroup.upstream.add(it) }
|
||||
|
||||
var approach: UniversePos? = null
|
||||
protected set
|
||||
|
||||
var position: Vector2d
|
||||
get() = Vector2d(xPosition, yPosition)
|
||||
set(value) {
|
||||
xPosition = value.x
|
||||
yPosition = value.y
|
||||
}
|
||||
|
||||
init {
|
||||
this.position = position
|
||||
}
|
||||
|
||||
fun enterOrbit(target: UniversePos, position: Vector2d, time: Double) {
|
||||
val direction = if (random.nextBoolean()) -1 else 1
|
||||
orbit = KOptional(Orbit(target, direction, time, position - this.position))
|
||||
approach = null
|
||||
}
|
||||
|
||||
fun toJson(): JsonObject {
|
||||
return Starbound.gson.toJsonTree(EntityJsonData(
|
||||
name = data.name,
|
||||
uuid = uuid.toStarboundString(),
|
||||
parameters = parameters,
|
||||
spawnTime = spawnTime,
|
||||
position = position,
|
||||
orbit = orbit.orNull(),
|
||||
)) as JsonObject
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,180 @@
|
||||
package ru.dbotthepony.kstarbound.world
|
||||
|
||||
import ru.dbotthepony.kommons.io.readUUID
|
||||
import ru.dbotthepony.kommons.io.writeUUID
|
||||
import ru.dbotthepony.kommons.util.KOptional
|
||||
import ru.dbotthepony.kommons.vector.Vector2d
|
||||
import ru.dbotthepony.kstarbound.GlobalDefaults
|
||||
import ru.dbotthepony.kstarbound.defs.SpawnTarget
|
||||
import ru.dbotthepony.kstarbound.defs.WarpAction
|
||||
import ru.dbotthepony.kstarbound.defs.WarpMode
|
||||
import ru.dbotthepony.kstarbound.defs.WorldID
|
||||
import ru.dbotthepony.kstarbound.defs.world.AsteroidsWorldParameters
|
||||
import ru.dbotthepony.kstarbound.defs.world.SkyParameters
|
||||
import ru.dbotthepony.kstarbound.defs.world.SkyType
|
||||
import ru.dbotthepony.kstarbound.io.readVector2d
|
||||
import ru.dbotthepony.kstarbound.io.writeStruct2d
|
||||
import ru.dbotthepony.kstarbound.network.syncher.legacyCodec
|
||||
import ru.dbotthepony.kstarbound.network.syncher.nativeCodec
|
||||
import java.io.DataInputStream
|
||||
import java.io.DataOutputStream
|
||||
import java.util.UUID
|
||||
import kotlin.math.PI
|
||||
import kotlin.math.absoluteValue
|
||||
|
||||
sealed class SystemWorldLocation {
|
||||
abstract fun write(stream: DataOutputStream, isLegacy: Boolean)
|
||||
abstract suspend fun resolve(system: SystemWorld): Vector2d?
|
||||
abstract suspend fun orbitalAction(system: SystemWorld): KOptional<Pair<WarpAction, WarpMode>>
|
||||
abstract suspend fun skyParameters(system: SystemWorld): SkyParameters
|
||||
|
||||
protected suspend fun appendParameters(parameters: SkyParameters, system: SystemWorld) {
|
||||
|
||||
}
|
||||
|
||||
object Transit : SystemWorldLocation() {
|
||||
override fun write(stream: DataOutputStream, isLegacy: Boolean) {
|
||||
stream.writeByte(0)
|
||||
}
|
||||
|
||||
override suspend fun resolve(system: SystemWorld): Vector2d? {
|
||||
return null
|
||||
}
|
||||
|
||||
override suspend fun orbitalAction(system: SystemWorld): KOptional<Pair<WarpAction, WarpMode>> {
|
||||
return KOptional()
|
||||
}
|
||||
|
||||
override suspend fun skyParameters(system: SystemWorld): SkyParameters {
|
||||
return GlobalDefaults.systemWorld.emptySkyParameters
|
||||
}
|
||||
}
|
||||
|
||||
// orbiting around specific planet
|
||||
data class Celestial(val position: UniversePos) : SystemWorldLocation() {
|
||||
override fun write(stream: DataOutputStream, isLegacy: Boolean) {
|
||||
stream.writeByte(1)
|
||||
position.write(stream, isLegacy)
|
||||
}
|
||||
|
||||
override suspend fun resolve(system: SystemWorld): Vector2d {
|
||||
return system.planetPosition(position)
|
||||
}
|
||||
|
||||
override suspend fun orbitalAction(system: SystemWorld): KOptional<Pair<WarpAction, WarpMode>> {
|
||||
return KOptional(WarpAction.World(WorldID.Celestial(position)) to WarpMode.BEAM_OR_DEPLOY)
|
||||
}
|
||||
|
||||
override suspend fun skyParameters(system: SystemWorld): SkyParameters {
|
||||
return SkyParameters.create(position, system.universe)
|
||||
}
|
||||
}
|
||||
|
||||
// orbiting around celestial body
|
||||
data class Orbit(val position: SystemWorld.Orbit) : SystemWorldLocation() {
|
||||
override fun write(stream: DataOutputStream, isLegacy: Boolean) {
|
||||
stream.writeByte(2)
|
||||
position.write(stream, isLegacy)
|
||||
}
|
||||
|
||||
override suspend fun resolve(system: SystemWorld): Vector2d {
|
||||
return system.orbitPosition(position)
|
||||
}
|
||||
|
||||
override suspend fun orbitalAction(system: SystemWorld): KOptional<Pair<WarpAction, WarpMode>> {
|
||||
return KOptional()
|
||||
}
|
||||
|
||||
override suspend fun skyParameters(system: SystemWorld): SkyParameters {
|
||||
if (position.target.isPlanet) {
|
||||
|
||||
}
|
||||
|
||||
return GlobalDefaults.systemWorld.emptySkyParameters
|
||||
}
|
||||
}
|
||||
|
||||
data class Entity(val uuid: UUID) : SystemWorldLocation() {
|
||||
override fun write(stream: DataOutputStream, isLegacy: Boolean) {
|
||||
stream.writeByte(3)
|
||||
stream.writeUUID(uuid)
|
||||
}
|
||||
|
||||
override suspend fun resolve(system: SystemWorld): Vector2d? {
|
||||
return system.entities[uuid]?.position
|
||||
}
|
||||
|
||||
override suspend fun orbitalAction(system: SystemWorld): KOptional<Pair<WarpAction, WarpMode>> {
|
||||
val action = system.entities[uuid]?.data?.warpAction ?: return KOptional()
|
||||
return KOptional(action to WarpMode.DEPLOY_ONLY)
|
||||
}
|
||||
|
||||
override suspend fun skyParameters(system: SystemWorld): SkyParameters {
|
||||
val get = system.entities[uuid] ?: return GlobalDefaults.systemWorld.emptySkyParameters
|
||||
return get.data.skyParameters
|
||||
}
|
||||
}
|
||||
|
||||
data class Position(val position: Vector2d) : SystemWorldLocation() {
|
||||
override fun write(stream: DataOutputStream, isLegacy: Boolean) {
|
||||
stream.writeByte(4)
|
||||
stream.writeStruct2d(position, isLegacy)
|
||||
}
|
||||
|
||||
override suspend fun resolve(system: SystemWorld): Vector2d {
|
||||
return position
|
||||
}
|
||||
|
||||
override suspend fun orbitalAction(system: SystemWorld): KOptional<Pair<WarpAction, WarpMode>> {
|
||||
// player can beam to asteroid fields simply by being in proximity to them
|
||||
for (child in system.universe.children(system.systemLocation)) {
|
||||
if ((system.planetPosition(child).length - position.length).absoluteValue > GlobalDefaults.systemWorld.asteroidBeamDistance) {
|
||||
continue
|
||||
}
|
||||
|
||||
val params = system.universe.parameters(child) ?: continue
|
||||
val visitable = params.visitableParameters ?: continue
|
||||
|
||||
if (visitable is AsteroidsWorldParameters) {
|
||||
val targetX = position.toAngle() / (2.0 * PI) * visitable.worldSize.x
|
||||
return KOptional(WarpAction.World(WorldID.Celestial(child), SpawnTarget.X(targetX)) to WarpMode.DEPLOY_ONLY)
|
||||
}
|
||||
}
|
||||
|
||||
return KOptional()
|
||||
}
|
||||
|
||||
override suspend fun skyParameters(system: SystemWorld): SkyParameters {
|
||||
for (child in system.universe.children(system.systemLocation)) {
|
||||
if ((system.planetPosition(child).length - position.length).absoluteValue > GlobalDefaults.systemWorld.asteroidBeamDistance) {
|
||||
continue
|
||||
}
|
||||
|
||||
val params = system.universe.parameters(child) ?: continue
|
||||
val visitable = params.visitableParameters ?: continue
|
||||
|
||||
if (visitable is AsteroidsWorldParameters) {
|
||||
return SkyParameters.create(child, system.universe)
|
||||
}
|
||||
}
|
||||
|
||||
return GlobalDefaults.systemWorld.emptySkyParameters
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
val CODEC = nativeCodec(::read, SystemWorldLocation::write)
|
||||
val LEGACY_CODEC = legacyCodec(::read, SystemWorldLocation::write)
|
||||
|
||||
fun read(stream: DataInputStream, isLegacy: Boolean): SystemWorldLocation {
|
||||
return when (val type = stream.readUnsignedByte()) {
|
||||
0 -> Transit
|
||||
1 -> Celestial(UniversePos(stream, isLegacy))
|
||||
2 -> Orbit(SystemWorld.Orbit(stream, isLegacy))
|
||||
3 -> Entity(stream.readUUID())
|
||||
4 -> Position(stream.readVector2d(isLegacy))
|
||||
else -> throw IllegalStateException("Unknown SystemWorldLocation type $type!")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -23,9 +23,16 @@ abstract class Universe {
|
||||
* are guaranteed to have unique x/y coordinates, and are meant to be viewed
|
||||
* from the top in 2d. The z-coordinate is there simpy as a validation
|
||||
* parameter.
|
||||
*
|
||||
* [callback] determines when to stop scanning (returning non empty KOptional will stop scanning)
|
||||
*/
|
||||
abstract suspend fun scanSystems(region: AABBi, includedTypes: Set<String>? = null): List<UniversePos>
|
||||
abstract suspend fun scanConstellationLines(region: AABBi): List<Pair<Vector2i, Vector2i>>
|
||||
abstract suspend fun <T> scanSystems(region: AABBi, callback: suspend (UniversePos) -> KOptional<T>): KOptional<T>
|
||||
abstract suspend fun scanConstellationLines(region: AABBi, aggressive: Boolean = false): List<Pair<Vector2i, Vector2i>>
|
||||
|
||||
/**
|
||||
* Similar to [scanSystems], but scans for ALL systems in given range, on multiple threads
|
||||
*/
|
||||
abstract suspend fun findSystems(region: AABBi, includedTypes: Set<String>? = null): List<UniversePos>
|
||||
|
||||
abstract suspend fun hasChildren(pos: UniversePos): Boolean
|
||||
abstract suspend fun children(pos: UniversePos): List<UniversePos>
|
||||
|
@ -34,7 +34,7 @@ import java.io.DataOutputStream
|
||||
* exists in a specific universe or not can be expressed.
|
||||
*/
|
||||
@JsonAdapter(UniversePos.Adapter::class)
|
||||
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) : Comparable<UniversePos> {
|
||||
constructor(stream: DataInputStream, isLegacy: Boolean) : this(stream.readVector3i(), if (isLegacy) stream.readInt() else stream.readVarInt(), if (isLegacy) stream.readInt() else stream.readVarInt())
|
||||
|
||||
init {
|
||||
@ -43,7 +43,21 @@ data class UniversePos(val location: Vector3i = Vector3i.ZERO, val planetOrbit:
|
||||
}
|
||||
|
||||
override fun toString(): String {
|
||||
return "${location.x},${location.y}${location.z}:$planetOrbit:$satelliteOrbit"
|
||||
if (planetOrbit == 0 && satelliteOrbit == 0)
|
||||
return "${location.x}:${location.y}:${location.z}"
|
||||
else if (satelliteOrbit == 0)
|
||||
return "${location.x}:${location.y}:${location.z}:$planetOrbit"
|
||||
else
|
||||
return "${location.x}:${location.y}:${location.z}:$planetOrbit:$satelliteOrbit"
|
||||
}
|
||||
|
||||
override fun compareTo(other: UniversePos): Int {
|
||||
var cmp = location.x.compareTo(other.location.x)
|
||||
if (cmp != 0) cmp = location.y.compareTo(other.location.y)
|
||||
if (cmp != 0) cmp = location.z.compareTo(other.location.z)
|
||||
if (cmp != 0) cmp = planetOrbit.compareTo(other.planetOrbit)
|
||||
if (cmp != 0) cmp = satelliteOrbit.compareTo(other.satelliteOrbit)
|
||||
return cmp
|
||||
}
|
||||
|
||||
val isSystem: Boolean
|
||||
@ -53,7 +67,7 @@ data class UniversePos(val location: Vector3i = Vector3i.ZERO, val planetOrbit:
|
||||
get() = planetOrbit != 0 && satelliteOrbit == 0
|
||||
|
||||
val isSatellite: Boolean
|
||||
get() = planetOrbit != 0 && satelliteOrbit == 0
|
||||
get() = planetOrbit != 0 && satelliteOrbit != 0
|
||||
|
||||
val orbitNumber: Int
|
||||
get() = if (isSatellite) satelliteOrbit else if (isPlanet) planetOrbit else 0
|
||||
@ -85,6 +99,15 @@ data class UniversePos(val location: Vector3i = Vector3i.ZERO, val planetOrbit:
|
||||
return this
|
||||
}
|
||||
|
||||
fun child(orbit: Int): UniversePos {
|
||||
if (isSatellite)
|
||||
throw IllegalStateException("Satellite can't have children!")
|
||||
else if (isPlanet)
|
||||
return UniversePos(location, planetOrbit, orbit)
|
||||
else
|
||||
return UniversePos(location, orbit)
|
||||
}
|
||||
|
||||
fun write(stream: DataOutputStream, isLegacy: Boolean) {
|
||||
stream.writeStruct3i(location)
|
||||
|
||||
@ -132,21 +155,7 @@ data class UniversePos(val location: Vector3i = Vector3i.ZERO, val planetOrbit:
|
||||
return ZERO
|
||||
else {
|
||||
try {
|
||||
val split = read.split(splitter)
|
||||
val x = split[0].toInt()
|
||||
val y = split[1].toInt()
|
||||
val z = split[2].toInt()
|
||||
|
||||
val planet = if (split.size > 3) split[3].toInt() else 0
|
||||
val orbit = if (split.size > 4) split[4].toInt() else 0
|
||||
|
||||
if (planet <= 0) // TODO: ??? Determine, if this is a bug in original code
|
||||
throw IndexOutOfBoundsException("Planetary orbit: $planet")
|
||||
|
||||
if (orbit < 0)
|
||||
throw IndexOutOfBoundsException("Satellite orbit: $orbit")
|
||||
|
||||
return UniversePos(Vector3i(x, y, z), planet, orbit)
|
||||
return parse(read)
|
||||
} catch (err: Throwable) {
|
||||
throw JsonSyntaxException("Error parsing UniversePos from string", err)
|
||||
}
|
||||
@ -163,5 +172,26 @@ data class UniversePos(val location: Vector3i = Vector3i.ZERO, val planetOrbit:
|
||||
|
||||
private val splitter = Regex("[ _:]")
|
||||
val ZERO = UniversePos()
|
||||
|
||||
fun parse(value: String): UniversePos {
|
||||
if (value.isBlank())
|
||||
return ZERO
|
||||
|
||||
val split = value.split(splitter)
|
||||
val x = split[0].toInt()
|
||||
val y = split[1].toInt()
|
||||
val z = split[2].toInt()
|
||||
|
||||
val planet = if (split.size > 3) split[3].toInt() else 0
|
||||
val orbit = if (split.size > 4) split[4].toInt() else 0
|
||||
|
||||
if (planet <= 0) // TODO: ??? Determine, if this is a bug in original code
|
||||
throw IndexOutOfBoundsException("Non-positive planetary orbit: $planet (in $value)")
|
||||
|
||||
if (orbit < 0)
|
||||
throw IndexOutOfBoundsException("Negative satellite orbit: $orbit (in $value)")
|
||||
|
||||
return UniversePos(Vector3i(x, y, z), planet, orbit)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,6 @@
|
||||
package ru.dbotthepony.kstarbound.world
|
||||
|
||||
import com.google.gson.JsonElement
|
||||
import com.google.gson.JsonObject
|
||||
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap
|
||||
import it.unimi.dsi.fastutil.ints.IntArraySet
|
||||
@ -8,6 +9,7 @@ import it.unimi.dsi.fastutil.objects.ObjectArraySet
|
||||
import org.apache.logging.log4j.LogManager
|
||||
import ru.dbotthepony.kommons.arrays.Object2DArray
|
||||
import ru.dbotthepony.kommons.collect.filterNotNull
|
||||
import ru.dbotthepony.kommons.gson.set
|
||||
import ru.dbotthepony.kommons.util.IStruct2d
|
||||
import ru.dbotthepony.kommons.util.IStruct2i
|
||||
import ru.dbotthepony.kommons.util.AABB
|
||||
@ -18,6 +20,7 @@ import ru.dbotthepony.kommons.vector.Vector2i
|
||||
import ru.dbotthepony.kstarbound.Starbound
|
||||
import ru.dbotthepony.kstarbound.defs.world.WorldStructure
|
||||
import ru.dbotthepony.kstarbound.defs.world.WorldTemplate
|
||||
import ru.dbotthepony.kstarbound.json.mergeJson
|
||||
import ru.dbotthepony.kstarbound.math.*
|
||||
import ru.dbotthepony.kstarbound.network.IPacket
|
||||
import ru.dbotthepony.kstarbound.network.packets.clientbound.SetPlayerStartPacket
|
||||
@ -239,7 +242,26 @@ abstract class World<This : World<This, ChunkType>, ChunkType : Chunk<This, Chun
|
||||
var centralStructure: WorldStructure = WorldStructure()
|
||||
|
||||
val protectedDungeonIDs = IntArraySet()
|
||||
val properties = JsonObject()
|
||||
protected val worldProperties = JsonObject()
|
||||
|
||||
fun copyProperties(): JsonObject = worldProperties.deepCopy()
|
||||
|
||||
fun updateProperties(properties: JsonObject) {
|
||||
mergeJson(worldProperties, properties)
|
||||
}
|
||||
|
||||
protected open fun setProperty0(key: String, value: JsonElement) {
|
||||
|
||||
}
|
||||
|
||||
fun setProperty(key: String, value: JsonElement) {
|
||||
if (worldProperties[key] == value)
|
||||
return
|
||||
|
||||
val copy = value.deepCopy()
|
||||
worldProperties[key] = copy
|
||||
setProperty0(key, copy)
|
||||
}
|
||||
|
||||
open fun setPlayerSpawn(position: Vector2d, respawnInWorld: Boolean) {
|
||||
playerSpawnPosition = position
|
||||
|
@ -213,4 +213,12 @@ data class WorldGeometry(val size: Vector2i, val loopX: Boolean, val loopY: Bool
|
||||
|
||||
return ObjectArraySet.ofUnchecked(*result.toTypedArray())
|
||||
}
|
||||
|
||||
fun diff(a: Vector2i, b: Vector2i): Vector2i {
|
||||
return Vector2i(x.diff(a.x, b.x), y.diff(a.y, b.y))
|
||||
}
|
||||
|
||||
fun diff(a: Vector2d, b: Vector2d): Vector2d {
|
||||
return Vector2d(x.diff(a.x, b.x), y.diff(a.y, b.y))
|
||||
}
|
||||
}
|
||||
|
@ -13,6 +13,8 @@ import ru.dbotthepony.kstarbound.client.StarboundClient
|
||||
import ru.dbotthepony.kstarbound.client.render.LayeredRenderer
|
||||
import ru.dbotthepony.kstarbound.client.world.ClientWorld
|
||||
import ru.dbotthepony.kstarbound.defs.EntityType
|
||||
import ru.dbotthepony.kstarbound.defs.InteractAction
|
||||
import ru.dbotthepony.kstarbound.defs.InteractRequest
|
||||
import ru.dbotthepony.kstarbound.defs.JsonDriven
|
||||
import ru.dbotthepony.kstarbound.network.packets.EntityDestroyPacket
|
||||
import ru.dbotthepony.kstarbound.network.syncher.InternedStringCodec
|
||||
@ -141,6 +143,10 @@ abstract class AbstractEntity(path: String) : JsonDriven(path), Comparable<Abstr
|
||||
}
|
||||
}
|
||||
|
||||
open fun interact(request: InteractRequest): InteractAction {
|
||||
return InteractAction.NONE
|
||||
}
|
||||
|
||||
var isRemote: Boolean = false
|
||||
|
||||
open fun tick() {
|
||||
|
@ -4,6 +4,7 @@ import com.google.common.collect.ImmutableList
|
||||
import com.google.common.collect.ImmutableMap
|
||||
import com.google.gson.JsonArray
|
||||
import com.google.gson.JsonElement
|
||||
import com.google.gson.JsonNull
|
||||
import com.google.gson.JsonObject
|
||||
import com.google.gson.JsonPrimitive
|
||||
import com.google.gson.TypeAdapter
|
||||
@ -34,6 +35,8 @@ import ru.dbotthepony.kommons.vector.Vector2d
|
||||
import ru.dbotthepony.kstarbound.client.world.ClientWorld
|
||||
import ru.dbotthepony.kstarbound.defs.DamageSource
|
||||
import ru.dbotthepony.kstarbound.defs.EntityType
|
||||
import ru.dbotthepony.kstarbound.defs.InteractAction
|
||||
import ru.dbotthepony.kstarbound.defs.InteractRequest
|
||||
import ru.dbotthepony.kstarbound.defs.`object`.ObjectType
|
||||
import ru.dbotthepony.kstarbound.defs.quest.QuestArcDescriptor
|
||||
import ru.dbotthepony.kstarbound.defs.tile.TileDamage
|
||||
@ -265,6 +268,25 @@ open class WorldObject(val config: Registry.Entry<ObjectDefinition>) : TileEntit
|
||||
}
|
||||
}
|
||||
|
||||
private val interactAction by LazyData {
|
||||
lookupProperty(JsonPath("interactAction")) { JsonNull.INSTANCE }
|
||||
}
|
||||
|
||||
private val interactData by LazyData {
|
||||
lookupProperty(JsonPath("interactData")) { JsonNull.INSTANCE }
|
||||
}
|
||||
|
||||
override fun interact(request: InteractRequest): InteractAction {
|
||||
val diff = world.geometry.diff(request.sourcePos, position)
|
||||
// val result =
|
||||
|
||||
if (!interactAction.isJsonNull) {
|
||||
return InteractAction(interactAction.asString, entityID, interactData)
|
||||
}
|
||||
|
||||
return super.interact(request)
|
||||
}
|
||||
|
||||
override fun invalidate() {
|
||||
super.invalidate()
|
||||
drawablesCache.invalidate()
|
||||
|
Loading…
Reference in New Issue
Block a user