We welcome the Universe

This commit is contained in:
DBotThePony 2024-02-29 14:59:54 +07:00
parent a06ed42268
commit 73cf5f596c
Signed by: DBot
GPG Key ID: DCC23B5715498507
49 changed files with 2133 additions and 539 deletions

View File

@ -2,7 +2,7 @@ kotlin.code.style=official
org.gradle.jvmargs=-Xmx2g -XX:MaxMetaspaceSize=512m
kotlinVersion=1.9.0
kommonsVersion=2.5.0
kommonsVersion=2.7.16
ffiVersion=2.2.13
lwjglVersion=3.3.0

View File

@ -1,16 +1,20 @@
package ru.dbotthepony.kstarbound
import it.unimi.dsi.fastutil.floats.FloatArrayList
import org.apache.logging.log4j.LogManager
import org.lwjgl.Version
import ru.dbotthepony.kommons.io.BTreeDB6
import ru.dbotthepony.kommons.io.ByteKey
import ru.dbotthepony.kommons.util.AABBi
import ru.dbotthepony.kommons.vector.Vector2d
import ru.dbotthepony.kommons.vector.Vector2i
import ru.dbotthepony.kstarbound.client.StarboundClient
import ru.dbotthepony.kstarbound.io.BTreeDB5
import ru.dbotthepony.kstarbound.server.IntegratedStarboundServer
import ru.dbotthepony.kstarbound.server.world.LegacyChunkSource
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.world.WorldGeometry
import ru.dbotthepony.kstarbound.world.entities.ItemEntity
import java.io.BufferedInputStream
@ -27,6 +31,22 @@ import java.util.zip.InflaterInputStream
private val LOGGER = LogManager.getLogger()
fun main() {
Starbound.addPakPath(File("J:\\Steam\\steamapps\\common\\Starbound\\assets\\packed.pak"))
Starbound.doBootstrap()
if (false) {
val data = ServerUniverse()
val t = System.nanoTime()
val result = data.scanConstellationLines(AABBi(Vector2i(-100, -100), Vector2i(100, 100))).get()
println(System.nanoTime() - t)
println(result)
data.close()
return
}
/*val db0 = BTreeDB5(File("F:\\SteamLibrary\\steamapps\\common\\Starbound - Unstable\\storage\\universe\\389760395_938904237_-238610574_5.world"))
val db2 = BTreeDB6.create(File("testdb.bdb"), sync = false)
@ -40,8 +60,8 @@ fun main() {
//Thread.sleep(6_000L)
val db = BTreeDB6(File("testdb.bdb"))
//val db = BTreeDB5(File("F:\\SteamLibrary\\steamapps\\common\\Starbound - Unstable\\storage\\universe\\389760395_938904237_-238610574_5.world"))
//val db = BTreeDB6(File("testdb.bdb"))
val db = BTreeDB5(File("F:\\SteamLibrary\\steamapps\\common\\Starbound - Unstable\\storage\\universe\\389760395_938904237_-238610574_5.world"))
//val db = BTreeDB(File("world.world"))
val meta = DataInputStream(BufferedInputStream(InflaterInputStream(ByteArrayInputStream(db.read(ByteKey(0, 0, 0, 0, 0)).get()), Inflater())))
@ -59,7 +79,6 @@ fun main() {
world.thread.start()
//Starbound.addFilePath(File("./unpacked_assets/"))
Starbound.addPakPath(File("J:\\Steam\\steamapps\\common\\Starbound\\assets\\packed.pak"))
/*for (folder in File("J:\\Steam\\steamapps\\workshop\\content\\211820").list()!!) {
val f = File("J:\\Steam\\steamapps\\workshop\\content\\211820\\$folder\\contents.pak")

View File

@ -21,7 +21,7 @@ import it.unimi.dsi.fastutil.objects.Object2ObjectMaps
import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap
import org.apache.logging.log4j.LogManager
import ru.dbotthepony.kommons.util.Either
import ru.dbotthepony.kstarbound.json.consumeNull
import ru.dbotthepony.kommons.gson.consumeNull
import java.lang.reflect.ParameterizedType
import java.util.concurrent.locks.ReentrantLock
import java.util.function.Supplier

View File

@ -17,6 +17,7 @@ import ru.dbotthepony.kommons.gson.Vector2iTypeAdapter
import ru.dbotthepony.kommons.gson.Vector4dTypeAdapter
import ru.dbotthepony.kommons.gson.Vector4iTypeAdapter
import ru.dbotthepony.kommons.util.MailboxExecutorService
import ru.dbotthepony.kstarbound.collect.WeightedList
import ru.dbotthepony.kstarbound.defs.*
import ru.dbotthepony.kstarbound.defs.image.Image
import ru.dbotthepony.kstarbound.defs.image.SpriteReference
@ -39,10 +40,13 @@ import ru.dbotthepony.kstarbound.json.factory.ArrayListAdapterFactory
import ru.dbotthepony.kstarbound.json.factory.ImmutableCollectionAdapterFactory
import ru.dbotthepony.kstarbound.json.factory.PairAdapterFactory
import ru.dbotthepony.kstarbound.math.*
import ru.dbotthepony.kstarbound.server.world.UniverseChunk
import ru.dbotthepony.kstarbound.util.ItemStack
import ru.dbotthepony.kstarbound.util.SBPattern
import ru.dbotthepony.kstarbound.util.HashTableInterner
import ru.dbotthepony.kstarbound.util.random.AbstractPerlinNoise
import ru.dbotthepony.kstarbound.util.traverseJsonPath
import ru.dbotthepony.kstarbound.world.UniversePos
import ru.dbotthepony.kstarbound.world.physics.Poly
import java.io.*
import java.lang.ref.Cleaner
@ -184,7 +188,10 @@ object Starbound : ISBFileLocator {
registerTypeAdapter(Vector2iTypeAdapter)
registerTypeAdapter(Vector4iTypeAdapter)
registerTypeAdapter(Vector4dTypeAdapter)
registerTypeAdapter(LineF::Adapter)
registerTypeAdapterFactory(Line2d.Companion)
registerTypeAdapterFactory(UniversePos.Companion)
registerTypeAdapterFactory(AbstractPerlinNoise.Companion)
registerTypeAdapterFactory(WeightedList.Companion)
// Функции
registerTypeAdapter(JsonFunction.CONSTRAINT_ADAPTER)
@ -210,6 +217,8 @@ object Starbound : ISBFileLocator {
registerTypeAdapterFactory(ItemReference.Factory(STRINGS))
registerTypeAdapterFactory(TreasurePoolDefinition.Companion)
registerTypeAdapterFactory(UniverseChunk.Companion)
registerTypeAdapter(Image.Companion)
registerTypeAdapterFactory(Poly.Companion)
@ -390,7 +399,7 @@ object Starbound : ISBFileLocator {
archivePaths.add(pak)
}
private fun doBootstrap() {
fun doBootstrap() {
if (!bootstrapped && !bootstrapping) {
bootstrapping = true
} else {

View File

@ -368,6 +368,7 @@ class StarboundClient private constructor(val clientID: Int) : Closeable {
mailbox.executeQueuedTasks()
var next = openglCleanQueue.poll() as CleanRef?
var i = 0
while (next != null) {
openglObjectsCleaned++
@ -383,6 +384,7 @@ class StarboundClient private constructor(val clientID: Int) : Closeable {
next.next?.prev = next.prev
}
if (++i > 400) break
next = openglCleanQueue.poll() as CleanRef?
}
}
@ -679,7 +681,7 @@ class StarboundClient private constructor(val clientID: Int) : Closeable {
blendFunc = BlendFunc.MULTIPLY_WITH_ALPHA
}
val spinner = ExecutionSpinner(mailbox, ::renderFrame, Starbound.TICK_TIME_ADVANCE_NANOS)
val spinner = ExecutionSpinner(::executeQueuedTasks, ::renderFrame, Starbound.TICK_TIME_ADVANCE_NANOS)
val settings = ClientSettings()
val viewportCells: ICellAccess = object : ICellAccess {

View File

@ -0,0 +1,71 @@
package ru.dbotthepony.kstarbound.collect
import com.google.common.collect.ImmutableList
import com.google.gson.Gson
import com.google.gson.TypeAdapter
import com.google.gson.TypeAdapterFactory
import com.google.gson.reflect.TypeToken
import com.google.gson.stream.JsonReader
import com.google.gson.stream.JsonWriter
import ru.dbotthepony.kommons.util.KOptional
import java.lang.reflect.ParameterizedType
import java.util.random.RandomGenerator
import kotlin.math.absoluteValue
class WeightedList<E>(val parent: ImmutableList<Pair<Double, E>>) {
constructor() : this(ImmutableList.of())
val isEmpty: Boolean
get() = parent.isEmpty()
val isNotEmpty: Boolean
get() = parent.isNotEmpty()
val sum = parent.stream().mapToDouble { it.first.absoluteValue }.sum()
fun sample(sampled: Double): KOptional<E> {
if (isEmpty)
return KOptional.empty()
var sum = 0.0
for ((v, e) in parent) {
sum += v
if (sum >= sampled)
return KOptional(e)
}
return KOptional(parent.last().second)
}
fun sample(random: RandomGenerator): KOptional<E> {
if (isEmpty)
return KOptional.empty()
return sample(random.nextDouble(sum))
}
companion object : TypeAdapterFactory {
override fun <T : Any> create(gson: Gson, type: TypeToken<T>): TypeAdapter<T>? {
if (type.rawType == WeightedList::class.java) {
val elemType = (type.type as? ParameterizedType)?.actualTypeArguments?.get(0) ?: return null
return object : TypeAdapter<WeightedList<Any?>>() {
private val parent = gson.getAdapter(TypeToken.getParameterized(ImmutableList::class.java, TypeToken.getParameterized(Pair::class.java, java.lang.Double::class.java, elemType).type)) as TypeAdapter<ImmutableList<Pair<Double, Any?>>>
override fun write(out: JsonWriter, value: WeightedList<Any?>?) {
parent.write(out, value?.parent)
}
override fun read(`in`: JsonReader): WeightedList<Any?>? {
val read = parent.read(`in`) ?: return null
return WeightedList(read)
}
} as TypeAdapter<T>
}
return null
}
}
}

View File

@ -1,12 +1,28 @@
package ru.dbotthepony.kstarbound.defs
import com.google.common.collect.ImmutableList
import com.google.common.collect.ImmutableMap
import com.google.gson.JsonObject
import it.unimi.dsi.fastutil.ints.Int2ObjectMap
import ru.dbotthepony.kommons.io.readVector2i
import ru.dbotthepony.kommons.io.writeStruct2i
import ru.dbotthepony.kommons.vector.Vector2i
import ru.dbotthepony.kstarbound.collect.WeightedList
import ru.dbotthepony.kstarbound.json.builder.JsonFactory
import ru.dbotthepony.kstarbound.util.random.AbstractPerlinNoise
import ru.dbotthepony.kstarbound.world.UniversePos
import java.io.DataInputStream
import java.io.DataOutputStream
@JsonFactory
data class CelestialNames(
val systemNames: WeightedList<String> = WeightedList(),
val systemPrefixNames: WeightedList<String> = WeightedList(),
val systemSuffixNames: WeightedList<String> = WeightedList(),
val planetarySuffixes: ImmutableList<String> = ImmutableList.of(),
val satelliteSuffixes: ImmutableList<String> = ImmutableList.of(),
)
@JsonFactory
data class CelestialBaseInformation(
val planetOrbitalLevels: Int = 1,
@ -31,3 +47,87 @@ data class CelestialBaseInformation(
stream.writeStruct2i(zCoordRange)
}
}
@JsonFactory
data class CelestialOrbitRegion(
val regionName: String,
val orbitRange: Vector2i,
val bodyProbability: Double,
val planetaryTypes: WeightedList<String> = WeightedList(),
val satelliteTypes: WeightedList<String> = WeightedList(),
)
@JsonFactory
data class CelestialPlanet(val parameters: CelestialParameters, val satellites: Int2ObjectMap<CelestialParameters>)
@JsonFactory
data class CelestialGenerationInformation(
val systemProbability: Double,
val constellationProbability: Double,
val constellationLineCountRange: Vector2i,
val constellationMaxTries: Int,
val maximumConstellationLineLength: Double,
val minimumConstellationLineLength: Double,
val minimumConstellationMagnitude: Double,
val minimumConstellationLineCloseness: Double,
val systemTypes: ImmutableMap<String, System>,
val systemTypePerlin: AbstractPerlinNoise,
val systemTypeBins: ImmutableList<Pair<Double, String>>,
val planetaryTypes: ImmutableMap<String, Planet>,
val satelliteTypes: ImmutableMap<String, Satellite>,
) {
init {
systemTypeBins.forEach {
require(it.second.isBlank() || it.second in systemTypes) { "System type is present in systemTypeBins, but not in systemTypes: ${it.second}" }
}
}
@JsonFactory
data class System(
var typeName: String = "",
/**
* This was present in original code, but never implemented
*/
val constellationCapable: Boolean = true,
val baseParameters: JsonObject = JsonObject(),
val variationParameters: ImmutableList<JsonObject> = ImmutableList.of(),
val orbitRegions: ImmutableList<CelestialOrbitRegion> = ImmutableList.of(),
)
@JsonFactory
data class Planet(
var typeName: String = "",
val satelliteProbability: Double = 0.0,
val maxSatelliteCount: Int? = null,
val baseParameters: JsonObject = JsonObject(),
val variationParameters: ImmutableList<JsonObject> = ImmutableList.of(),
val orbitParameters: JsonObject = JsonObject(),
)
@JsonFactory
data class Satellite(
var typeName: String = "",
val baseParameters: JsonObject = JsonObject(),
val variationParameters: ImmutableList<JsonObject> = ImmutableList.of(),
val orbitParameters: ImmutableMap<String, ImmutableList<JsonObject>> = ImmutableMap.of(),
)
init {
planetaryTypes.entries.forEach {
it.value.typeName = it.key
}
systemTypes.entries.forEach {
it.value.typeName = it.key
}
satelliteTypes.entries.forEach {
it.value.typeName = it.key
}
}
}
@JsonFactory
data class CelestialParameters(val coordinate: UniversePos, val seed: Long, val name: String, val parameters: JsonObject)

View File

@ -19,16 +19,16 @@ import ru.dbotthepony.kstarbound.client.gl.vertex.GeometryType
import ru.dbotthepony.kstarbound.client.render.IGeometryLayer
import ru.dbotthepony.kstarbound.defs.image.SpriteReference
import ru.dbotthepony.kstarbound.json.builder.JsonFactory
import ru.dbotthepony.kstarbound.json.consumeNull
import ru.dbotthepony.kstarbound.math.LineF
import ru.dbotthepony.kstarbound.json.contains
import ru.dbotthepony.kommons.gson.consumeNull
import ru.dbotthepony.kommons.gson.contains
import ru.dbotthepony.kstarbound.math.Line2d
sealed class Drawable(val position: Vector2f, val color: RGBAColor, val fullbright: Boolean) {
@JsonFactory
data class Transformations(val centered: Boolean = false, val rotation: Float = 0f, val mirrored: Boolean = false, val scale: Either<Float, Vector2f> = Either.left(1f))
class Line(
val line: LineF,
val line: Line2d,
val width: Float,
position: Vector2f = Vector2f.ZERO,
color: RGBAColor = RGBAColor.WHITE,
@ -143,7 +143,7 @@ sealed class Drawable(val position: Vector2f, val color: RGBAColor, val fullbrig
}
class Adapter(gson: Gson) : TypeAdapter<Drawable>() {
private val lines = gson.getAdapter(LineF::class.java)
private val lines = gson.getAdapter(Line2d::class.java)
private val objects = gson.getAdapter(JsonObject::class.java)
private val vectors = gson.getAdapter(Vector2f::class.java)
private val vectors3 = gson.getAdapter(Vector3f::class.java)

View File

@ -13,7 +13,7 @@ import ru.dbotthepony.kstarbound.Registry
import ru.dbotthepony.kstarbound.defs.item.api.IItemDefinition
import ru.dbotthepony.kstarbound.json.builder.FactoryAdapter
import ru.dbotthepony.kstarbound.json.builder.JsonFactory
import ru.dbotthepony.kstarbound.json.consumeNull
import ru.dbotthepony.kommons.gson.consumeNull
import ru.dbotthepony.kstarbound.util.ItemStack
/**

View File

@ -8,7 +8,7 @@ import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap
import ru.dbotthepony.kommons.util.Either
import ru.dbotthepony.kstarbound.Starbound
import ru.dbotthepony.kstarbound.util.AssetPathStack
import ru.dbotthepony.kstarbound.json.set
import ru.dbotthepony.kommons.gson.set
import java.util.function.Consumer
import java.util.function.Function
import java.util.function.Supplier
@ -215,7 +215,7 @@ abstract class JsonDriven(val path: String) {
private val never = Supplier { throw NoSuchElementException() }
@JvmStatic
protected fun mergeNoCopy(a: JsonObject, b: JsonObject): JsonObject {
fun mergeNoCopy(a: JsonObject, b: JsonObject): JsonObject {
for ((k, v) in b.entrySet()) {
val existing = a[k]

View File

@ -13,8 +13,8 @@ import com.google.gson.stream.JsonReader
import com.google.gson.stream.JsonToken
import com.google.gson.stream.JsonWriter
import ru.dbotthepony.kstarbound.Starbound
import ru.dbotthepony.kstarbound.json.consumeNull
import ru.dbotthepony.kstarbound.json.value
import ru.dbotthepony.kommons.gson.consumeNull
import ru.dbotthepony.kommons.gson.value
import ru.dbotthepony.kstarbound.util.AssetPathStack
sealed class JsonReference<E : JsonElement?>(val path: String?, val fullPath: String?) {

View File

@ -0,0 +1,28 @@
package ru.dbotthepony.kstarbound.defs
import ru.dbotthepony.kstarbound.json.builder.JsonFactory
@JsonFactory
data class PerlinNoiseParameters(
val type: Type = Type.PERLIN,
val seed: Long? = null,
val scale: Int = 512,
val octaves: Int = 1,
val gain: Double = 2.0,
val offset: Double = 1.0,
val beta: Double = 1.0,
val alpha: Double = 2.0,
val frequency: Double = 1.0,
val amplitude: Double = 1.0,
val bias: Double = 0.0,
) {
init {
require(scale >= 16) { "Too little perlin noise scale" }
}
enum class Type {
PERLIN,
BILLOW,
RIDGED_MULTI;
}
}

View File

@ -7,9 +7,9 @@ import com.google.gson.JsonSyntaxException
import com.google.gson.TypeAdapter
import com.google.gson.stream.JsonReader
import com.google.gson.stream.JsonWriter
import ru.dbotthepony.kstarbound.json.consumeNull
import ru.dbotthepony.kstarbound.json.contains
import ru.dbotthepony.kstarbound.json.get
import ru.dbotthepony.kommons.gson.consumeNull
import ru.dbotthepony.kommons.gson.contains
import ru.dbotthepony.kommons.gson.get
enum class StatModifierType(vararg names: String) {
BASE_ADDITION("value", "baseAddition"),

View File

@ -31,10 +31,10 @@ import ru.dbotthepony.kstarbound.Starbound
import ru.dbotthepony.kstarbound.IStarboundFile
import ru.dbotthepony.kstarbound.client.StarboundClient
import ru.dbotthepony.kstarbound.client.gl.GLTexture2D
import ru.dbotthepony.kstarbound.json.consumeNull
import ru.dbotthepony.kstarbound.json.contains
import ru.dbotthepony.kstarbound.json.get
import ru.dbotthepony.kstarbound.json.getObject
import ru.dbotthepony.kommons.gson.consumeNull
import ru.dbotthepony.kommons.gson.contains
import ru.dbotthepony.kommons.gson.get
import ru.dbotthepony.kommons.gson.getObject
import java.io.BufferedInputStream
import java.io.FileNotFoundException
import java.lang.ref.Reference

View File

@ -6,7 +6,7 @@ import com.google.gson.stream.JsonReader
import com.google.gson.stream.JsonWriter
import ru.dbotthepony.kstarbound.Starbound
import ru.dbotthepony.kstarbound.defs.AssetPath
import ru.dbotthepony.kstarbound.json.consumeNull
import ru.dbotthepony.kommons.gson.consumeNull
import ru.dbotthepony.kstarbound.util.AssetPathStack
import ru.dbotthepony.kstarbound.util.SBPattern

View File

@ -14,7 +14,7 @@ import ru.dbotthepony.kommons.util.KOptional
import ru.dbotthepony.kstarbound.lua.StateMachine
import ru.dbotthepony.kstarbound.lua.from
import ru.dbotthepony.kstarbound.lua.toJsonObject
import ru.dbotthepony.kstarbound.json.get
import ru.dbotthepony.kommons.gson.get
import java.util.function.Supplier
fun ItemDescriptor(data: JsonElement): ItemDescriptor {

View File

@ -15,7 +15,7 @@ import com.google.gson.stream.JsonWriter
import ru.dbotthepony.kommons.util.Either
import ru.dbotthepony.kstarbound.Registry
import ru.dbotthepony.kstarbound.defs.ItemReference
import ru.dbotthepony.kstarbound.json.consumeNull
import ru.dbotthepony.kommons.gson.consumeNull
import ru.dbotthepony.kstarbound.json.stream
import ru.dbotthepony.kstarbound.util.ItemStack
import ru.dbotthepony.kstarbound.util.WriteOnce

View File

@ -21,14 +21,14 @@ import ru.dbotthepony.kstarbound.defs.StatModifier
import ru.dbotthepony.kstarbound.defs.item.TreasurePoolDefinition
import ru.dbotthepony.kstarbound.defs.tile.TileDamageConfig
import ru.dbotthepony.kstarbound.json.builder.JsonFactory
import ru.dbotthepony.kstarbound.json.consumeNull
import ru.dbotthepony.kommons.gson.consumeNull
import ru.dbotthepony.kstarbound.json.listAdapter
import ru.dbotthepony.kstarbound.json.stream
import ru.dbotthepony.kstarbound.math.PeriodicFunction
import ru.dbotthepony.kstarbound.json.contains
import ru.dbotthepony.kstarbound.json.get
import ru.dbotthepony.kstarbound.json.getArray
import ru.dbotthepony.kstarbound.json.set
import ru.dbotthepony.kommons.gson.contains
import ru.dbotthepony.kommons.gson.get
import ru.dbotthepony.kommons.gson.getArray
import ru.dbotthepony.kommons.gson.set
data class ObjectDefinition(
val objectName: String,

View File

@ -10,6 +10,7 @@ import com.google.gson.TypeAdapter
import com.google.gson.reflect.TypeToken
import com.google.gson.stream.JsonReader
import com.google.gson.stream.JsonWriter
import ru.dbotthepony.kommons.gson.clear
import ru.dbotthepony.kommons.util.AABB
import ru.dbotthepony.kommons.util.AABBi
import ru.dbotthepony.kommons.vector.Vector2d
@ -20,13 +21,12 @@ import ru.dbotthepony.kstarbound.client.render.RenderLayer
import ru.dbotthepony.kstarbound.defs.Drawable
import ru.dbotthepony.kstarbound.defs.JsonReference
import ru.dbotthepony.kstarbound.defs.tile.BuiltinMetaMaterials
import ru.dbotthepony.kstarbound.json.clear
import ru.dbotthepony.kstarbound.json.consumeNull
import ru.dbotthepony.kommons.gson.consumeNull
import ru.dbotthepony.kstarbound.json.listAdapter
import ru.dbotthepony.kstarbound.json.setAdapter
import ru.dbotthepony.kstarbound.json.contains
import ru.dbotthepony.kstarbound.json.get
import ru.dbotthepony.kstarbound.json.set
import ru.dbotthepony.kommons.gson.contains
import ru.dbotthepony.kommons.gson.get
import ru.dbotthepony.kommons.gson.set
import ru.dbotthepony.kstarbound.world.Side
import kotlin.math.PI

View File

@ -2,22 +2,16 @@ package ru.dbotthepony.kstarbound.io
import it.unimi.dsi.fastutil.io.FastByteArrayInputStream
import it.unimi.dsi.fastutil.longs.LongArrayList
import ru.dbotthepony.kommons.io.ByteDataBTreeDB
import ru.dbotthepony.kommons.io.ByteKey
import ru.dbotthepony.kommons.io.readByteKeyRaw
import ru.dbotthepony.kommons.io.readVarInt
import ru.dbotthepony.kommons.util.CarriedExecutor
import ru.dbotthepony.kommons.util.KOptional
import ru.dbotthepony.kstarbound.Starbound
import java.io.Closeable
import java.io.DataInputStream
import java.io.File
import java.io.InputStream
import java.io.RandomAccessFile
import java.util.*
import java.util.concurrent.CompletableFuture
import java.util.concurrent.TimeUnit
import java.util.function.Supplier
import kotlin.collections.ArrayList
private fun readHeader(reader: RandomAccessFile, required: Char) {
val read = reader.read()
@ -51,7 +45,7 @@ private enum class TreeBlockType(val identity: String) {
}
}
class BTreeDB5(override val file: File) : ByteDataBTreeDB<ByteKey>() {
class BTreeDB5(val file: File) : Closeable {
private val reader = RandomAccessFile(file, "r")
init {
@ -65,7 +59,7 @@ class BTreeDB5(override val file: File) : ByteDataBTreeDB<ByteKey>() {
readHeader(reader, '5')
}
override val blockSize = reader.readInt()
val blockSize = reader.readInt()
val dbNameRaw = ByteArray(16).also { reader.read(it) }
val keySize = reader.readInt()
val useNodeTwo = reader.readBoolean()
@ -146,17 +140,13 @@ class BTreeDB5(override val file: File) : ByteDataBTreeDB<ByteKey>() {
reader.close()
}
override fun write(key: ByteKey, value: ByteArray, offset: Int, length: Int) {
throw UnsupportedOperationException()
}
override fun findAllKeys(): List<ByteKey> {
fun findAllKeys(): List<ByteKey> {
val list = ArrayList<ByteKey>()
doFindAllKeys(rootNodeIndex, list)
return list
}
override fun contains(key: ByteKey): Boolean {
fun contains(key: ByteKey): Boolean {
seekBlock(rootNodeIndex)
var blockStream = BlockInputStream()
@ -218,7 +208,7 @@ class BTreeDB5(override val file: File) : ByteDataBTreeDB<ByteKey>() {
return false
}
override fun read(key: ByteKey): KOptional<ByteArray> {
fun read(key: ByteKey): KOptional<ByteArray> {
require(key.size == keySize) { "Key provided is ${key.size} in size, while $keySize is required" }
seekBlock(rootNodeIndex)

View File

@ -13,6 +13,7 @@ import ru.dbotthepony.kommons.io.readBinaryString
import ru.dbotthepony.kommons.io.readSignedVarLong
import ru.dbotthepony.kommons.io.readString
import ru.dbotthepony.kommons.io.readVarInt
import ru.dbotthepony.kstarbound.Starbound
import java.io.DataInputStream
import java.io.EOFException
import java.io.InputStream
@ -28,7 +29,7 @@ fun DataInputStream.readJsonElement(): JsonElement {
BinaryJsonReader.TYPE_DOUBLE -> JsonPrimitive(readDouble())
BinaryJsonReader.TYPE_BOOLEAN -> InternedJsonElementAdapter.of(readBoolean())
BinaryJsonReader.TYPE_INT -> JsonPrimitive(readSignedVarLong())
BinaryJsonReader.TYPE_STRING -> JsonPrimitive(readBinaryString())
BinaryJsonReader.TYPE_STRING -> JsonPrimitive(Starbound.STRINGS.intern(readBinaryString()))
BinaryJsonReader.TYPE_ARRAY -> readJsonArray()
BinaryJsonReader.TYPE_OBJECT -> readJsonObject()
else -> throw JsonParseException("Unknown element type $id")
@ -47,7 +48,7 @@ fun DataInputStream.readJsonObject(): JsonObject {
for (i in 0 until values) {
val key = try {
readBinaryString()
Starbound.STRINGS.intern(readBinaryString())
} catch(err: Throwable) {
throw JsonSyntaxException("Reading json object at $i", err)
}
@ -216,7 +217,7 @@ class BinaryJsonReader(private val stream: DataInputStream) : JsonReader(unreada
}
private inner class StringReader(val length: Int) : NothingReader("string", JsonToken.STRING) {
private val value by lazy(LazyThreadSafetyMode.NONE) { stream.readString(length) }
private val value by lazy(LazyThreadSafetyMode.NONE) { Starbound.STRINGS.intern(stream.readString(length)) }
override fun skipValue() {
popstack(this)

View File

@ -27,7 +27,17 @@ fun DataOutputStream.writeJsonElement(value: JsonElement) {
}
else -> {
throw IllegalArgumentException("Unknown number type: ${v::class} ($v)")
// throw IllegalArgumentException("Unknown number type: ${v::class} ($v)")
// hi com.google.gson.internal.LazilyParsedNumber
val doubleBits by lazy { v.toDouble() }
if (v.toString().contains('.') || doubleBits % 1.0 != 0.0) {
write(BinaryJsonReader.TYPE_DOUBLE)
writeDouble(doubleBits)
} else {
write(BinaryJsonReader.TYPE_INT)
writeSignedVarLong(v.toLong())
}
}
}
} else if (value.isString) {

View File

@ -8,6 +8,7 @@ import com.google.gson.reflect.TypeToken
import com.google.gson.stream.JsonReader
import com.google.gson.stream.JsonWriter
import it.unimi.dsi.fastutil.objects.*
import ru.dbotthepony.kommons.gson.consumeNull
import java.lang.reflect.ParameterizedType
class FastutilTypeAdapterFactory(private val interner: Interner<String>) : TypeAdapterFactory {

View File

@ -17,133 +17,6 @@ import com.google.gson.stream.JsonToken
import com.google.gson.stream.JsonWriter
import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet
fun <T> TypeAdapter<T>.transformRead(transformer: (T) -> T): TypeAdapter<T> {
return object : TypeAdapter<T>() {
override fun write(out: JsonWriter, value: T) {
return this@transformRead.write(out, value)
}
override fun read(`in`: JsonReader): T {
return transformer(this@transformRead.read(`in`))
}
}
}
fun <T> TypeAdapter<T>.transformWrite(transformer: (T) -> T): TypeAdapter<T> {
return object : TypeAdapter<T>() {
override fun write(out: JsonWriter, value: T) {
return this@transformWrite.write(out, transformer(value))
}
override fun read(`in`: JsonReader): T {
return this@transformWrite.read(`in`)
}
}
}
fun <In, Out> TypeAdapter<In>.transform(read: (In) -> Out, write: (Out) -> In): TypeAdapter<Out> {
return object : TypeAdapter<Out>() {
override fun write(out: JsonWriter, value: Out) {
return this@transform.write(out, write(value))
}
override fun read(`in`: JsonReader): Out {
return read(this@transform.read(`in`))
}
}
}
fun <T> TypeAdapter<T>.ifString(reader: (String) -> T): TypeAdapter<T> {
return object : TypeAdapter<T>() {
override fun write(out: JsonWriter, value: T) {
return this@ifString.write(out, value)
}
override fun read(`in`: JsonReader): T {
if (`in`.peek() == JsonToken.STRING) {
return reader(`in`.nextString())
}
return this@ifString.read(`in`)
}
}
}
fun <T> TypeAdapter<T?>.neverNull(): TypeAdapter<T> {
return object : TypeAdapter<T>() {
override fun write(out: JsonWriter, value: T) {
this@neverNull.write(out, value)
}
override fun read(`in`: JsonReader): T {
val path = `in`.path
return this@neverNull.read(`in`) ?: throw JsonSyntaxException("Value was null near $path")
}
}
}
fun <T> TypeAdapter<T>.allowNull(): TypeAdapter<T?> = nullSafe()
@Deprecated("Небезопасно, надо использовать другой путь")
fun TypeAdapterFactory.ifString(reader: (String) -> Any): TypeAdapterFactory {
return object : TypeAdapterFactory {
override fun <T : Any?> create(gson: Gson, type: TypeToken<T>): TypeAdapter<T>? {
return this@ifString.create(gson, type)?.ifString(reader as (String) -> T)
}
}
}
fun JsonReader.consumeNull(): Boolean {
if (peek() == JsonToken.NULL) {
nextNull()
return true
}
return false
}
fun JsonWriter.value(element: JsonPrimitive) {
if (element.isBoolean) {
value(element.asBoolean)
} else if (element.isNumber) {
value(element.asNumber)
} else if (element.isString) {
value(element.asString)
} else {
throw IllegalArgumentException(element.toString())
}
}
fun JsonWriter.value(element: JsonNull) {
nullValue()
}
fun JsonWriter.value(element: JsonArray) {
beginArray()
for (v in element) value(v)
endArray()
}
fun JsonWriter.value(element: JsonObject) {
beginObject()
for ((k, v) in element.entrySet()) {
name(k)
value(v)
}
endObject()
}
fun JsonWriter.value(element: JsonElement?) {
when (element) {
is JsonPrimitive -> value(element)
is JsonNull -> value(element)
is JsonArray -> value(element)
is JsonObject -> value(element)
null -> nullValue()
else -> throw IllegalArgumentException(element.toString())
}
}
inline fun <reified C : Collection<E>, reified E> Gson.collectionAdapter(): TypeAdapter<C> {
return getAdapter(TypeToken.getParameterized(C::class.java, E::class.java)) as TypeAdapter<C>
}
@ -152,6 +25,14 @@ inline fun <reified E> Gson.listAdapter(): TypeAdapter<ImmutableList<E>> {
return collectionAdapter()
}
inline fun <reified A, reified B> Gson.pairAdapter(): TypeAdapter<Pair<A, B>> {
return getAdapter(TypeToken.getParameterized(Pair::class.java, A::class.java, B::class.java)) as TypeAdapter<Pair<A, B>>
}
inline fun <reified A, reified B> Gson.pairListAdapter(): TypeAdapter<ImmutableList<Pair<A, B>>> {
return getAdapter(TypeToken.getParameterized(ImmutableList::class.java, TypeToken.getParameterized(Pair::class.java, A::class.java, B::class.java).type)) as TypeAdapter<ImmutableList<Pair<A, B>>>
}
inline fun <reified E> Gson.mutableListAdapter(): TypeAdapter<ArrayList<E>> {
return collectionAdapter()
}
@ -160,275 +41,10 @@ inline fun <reified E> Gson.setAdapter(): TypeAdapter<ImmutableSet<E>> {
return collectionAdapter()
}
inline fun <reified A, reified B> Gson.pairSetAdapter(): TypeAdapter<ImmutableSet<Pair<A, B>>> {
return getAdapter(TypeToken.getParameterized(ImmutableSet::class.java, TypeToken.getParameterized(Pair::class.java, A::class.java, B::class.java).type)) as TypeAdapter<ImmutableSet<Pair<A, B>>>
}
inline fun <reified E> Gson.mutableSetAdapter(): TypeAdapter<ObjectOpenHashSet<E>> {
return collectionAdapter()
}
fun JsonArray(elements: Collection<JsonElement>): JsonArray {
return JsonArray(elements.size).also { elements.forEach(it::add) }
}
fun JsonArray.clear() {
while (size() > 0) {
remove(size() - 1)
}
}
operator fun JsonObject.set(key: String, value: JsonElement?) { add(key, value) }
operator fun JsonObject.set(key: String, value: String) { add(key, JsonPrimitive(value)) }
operator fun JsonObject.set(key: String, value: Int) { add(key, JsonPrimitive(value)) }
operator fun JsonObject.set(key: String, value: Long) { add(key, JsonPrimitive(value)) }
operator fun JsonObject.set(key: String, value: Float) { add(key, JsonPrimitive(value)) }
operator fun JsonObject.set(key: String, value: Double) { add(key, JsonPrimitive(value)) }
operator fun JsonObject.set(key: String, value: Boolean) { add(key, InternedJsonElementAdapter.of(value)) }
operator fun JsonObject.set(key: String, value: Nothing?) { add(key, JsonNull.INSTANCE) }
operator fun JsonObject.contains(key: String): Boolean = has(key)
fun JsonArray.get(key: Int, default: Boolean): Boolean {
if (key !in 0 until size())
return default
val value = this[key]
if (value !is JsonPrimitive || !value.isBoolean) {
throw JsonSyntaxException("Expected boolean at $key; got $value")
}
return value.asBoolean
}
fun JsonObject.get(key: String, default: Boolean): Boolean {
if (!has(key))
return default
val value = this[key]
if (value !is JsonPrimitive || !value.isBoolean) {
throw JsonSyntaxException("Expected boolean at $key; got $value")
}
return value.asBoolean
}
fun JsonObject.get(key: String, default: String): String {
if (!has(key))
return default
val value = this[key]
if (value !is JsonPrimitive || !value.isString) {
throw JsonSyntaxException("Expected string at $key; got $value")
}
return value.asString
}
fun JsonArray.get(key: Int, default: String): String {
if (key !in 0 until size())
return default
val value = this[key]
if (value !is JsonPrimitive || !value.isString) {
throw JsonSyntaxException("Expected string at $key; got $value")
}
return value.asString
}
fun JsonObject.get(key: String, default: Int): Int {
if (!has(key))
return default
val value = this[key]
if (value !is JsonPrimitive || !value.isNumber) {
throw JsonSyntaxException("Expected integer at $key; got $value")
}
return value.asInt
}
fun JsonArray.get(key: Int, default: Int): Int {
if (key !in 0 until size())
return default
val value = this[key]
if (value !is JsonPrimitive || !value.isNumber) {
throw JsonSyntaxException("Expected integer at $key; got $value")
}
return value.asInt
}
fun JsonObject.get(key: String, default: Long): Long {
if (!has(key))
return default
val value = this[key]
if (value !is JsonPrimitive || !value.isNumber) {
throw JsonSyntaxException("Expected long at $key; got $value")
}
return value.asLong
}
fun JsonArray.get(key: Int, default: Long): Long {
if (key !in 0 until size())
return default
val value = this[key]
if (value !is JsonPrimitive || !value.isNumber) {
throw JsonSyntaxException("Expected long at $key; got $value")
}
return value.asLong
}
fun JsonObject.get(key: String, default: Double): Double {
if (!has(key))
return default
val value = this[key]
if (value !is JsonPrimitive || !value.isNumber) {
throw JsonSyntaxException("Expected double at $key; got $value")
}
return value.asDouble
}
fun JsonArray.get(key: Int, default: Double): Double {
if (key !in 0 until size())
return default
val value = this[key]
if (value !is JsonPrimitive || !value.isNumber) {
throw JsonSyntaxException("Expected double at $key; got $value")
}
return value.asDouble
}
fun JsonObject.get(key: String, default: JsonObject): JsonObject {
if (!has(key))
return default
val value = this[key]
if (value !is JsonObject)
throw JsonSyntaxException("Expected json object at $key; got $value")
return value
}
inline fun <reified T : JsonElement> JsonObject.get(key: String, default: () -> T): T {
if (!has(key))
return default.invoke()
val value = this[key]
if (value !is T)
throw JsonSyntaxException("Expected json object at $key; got $value")
return value
}
fun JsonArray.get(key: Int, default: JsonObject): JsonObject {
if (key !in 0 until size())
return default
val value = this[key]
if (value !is JsonObject)
throw JsonSyntaxException("Expected json object at $key; got $value")
return value
}
inline fun JsonArray.get(key: Int, default: () -> JsonObject): JsonObject {
if (key !in 0 until size())
return default.invoke()
val value = this[key]
if (value !is JsonObject)
throw JsonSyntaxException("Expected json object at $key; got $value")
return value
}
fun JsonObject.get(key: String, default: JsonArray): JsonArray {
if (!has(key))
return default
val value = this[key]
if (value !is JsonArray)
throw JsonSyntaxException("Expected json array at $key; got $value")
return value
}
fun JsonArray.get(key: Int, default: JsonArray): JsonArray {
if (key !in 0 until size())
return default
val value = this[key]
if (value !is JsonArray)
throw JsonSyntaxException("Expected json array at $key; got $value")
return value
}
inline fun <reified T : JsonElement> JsonArray.get(key: Int, default: () -> T): T {
if (key !in 0 until size())
return default.invoke()
val value = this[key]
if (value !is T)
throw JsonSyntaxException("Expected json array at $key; got $value")
return value
}
fun JsonObject.get(key: String, default: JsonPrimitive): JsonElement {
if (!has(key))
return default
return this[key]
}
fun JsonArray.get(key: Int, default: JsonPrimitive): JsonElement {
if (key !in 0 until size())
return default
return this[key]
}
fun <T> JsonObject.get(key: String, type: TypeAdapter<T>): T {
return get(key, type) { throw JsonSyntaxException("Expected value at $key, got nothing") }
}
inline fun <T> JsonObject.get(key: String, type: TypeAdapter<out T>, orElse: () -> T): T {
if (!has(key))
return orElse.invoke()
return type.fromJsonTree(this[key])
}
fun JsonObject.getArray(key: String): JsonArray {
if (!has(key)) throw JsonSyntaxException("Expected array at $key, got nothing")
return this[key] as? JsonArray ?: throw JsonSyntaxException("Expected array at $key, got ${this[key]}")
}
fun JsonObject.getObject(key: String): JsonObject {
if (!has(key)) throw JsonSyntaxException("Expected object at $key, got nothing")
return this[key] as? JsonObject ?: throw JsonSyntaxException("Expected object at $key, got ${this[key]}")
}

View File

@ -27,8 +27,8 @@ import ru.dbotthepony.kstarbound.Starbound
import ru.dbotthepony.kstarbound.defs.util.enrollList
import ru.dbotthepony.kstarbound.defs.util.enrollMap
import ru.dbotthepony.kstarbound.defs.util.flattenJsonElement
import ru.dbotthepony.kstarbound.json.consumeNull
import ru.dbotthepony.kstarbound.json.value
import ru.dbotthepony.kommons.gson.consumeNull
import ru.dbotthepony.kommons.gson.value
import java.lang.reflect.Constructor
import java.util.Collections
import java.util.function.Function

View File

@ -8,7 +8,7 @@ import com.google.gson.reflect.TypeToken
import com.google.gson.stream.JsonReader
import com.google.gson.stream.JsonToken
import com.google.gson.stream.JsonWriter
import ru.dbotthepony.kstarbound.json.consumeNull
import ru.dbotthepony.kommons.gson.consumeNull
import java.lang.reflect.ParameterizedType
object PairAdapterFactory : TypeAdapterFactory {
@ -61,12 +61,12 @@ object PairAdapterFactory : TypeAdapterFactory {
while (`in`.hasNext()) {
when (`in`.nextName().lowercase()) {
"left", "first", "a" -> {
"left", "first", "a", "weight" -> {
left = adapter0.read(`in`)
leftPresent = true
}
"right", "second", "b" -> {
"right", "second", "b", "item" -> {
right = adapter1.read(`in`)
rightPresent = true
}

View File

@ -1,24 +0,0 @@
package ru.dbotthepony.kstarbound.math
/**
* Выполняет скалярное умножение между a и векторным произведением b, c на стеке
*/
fun scalarDotWithCross(
ax: Double,
ay: Double,
az: Double,
bx: Double,
by: Double,
bz: Double,
cx: Double,
cy: Double,
cz: Double,
): Double {
val crossX = by * cz - bz * cy
val crossY = bz * cx - bx * cz
val crossZ = bx * cy - by * cx
return ax * crossX + ay * crossY + az * crossZ
}

View File

@ -0,0 +1,142 @@
package ru.dbotthepony.kstarbound.math
import com.google.gson.Gson
import com.google.gson.TypeAdapter
import com.google.gson.TypeAdapterFactory
import com.google.gson.reflect.TypeToken
import com.google.gson.stream.JsonReader
import com.google.gson.stream.JsonWriter
import ru.dbotthepony.kommons.gson.consumeNull
import ru.dbotthepony.kommons.util.KOptional
import ru.dbotthepony.kommons.vector.Vector2d
import kotlin.math.absoluteValue
private operator fun Vector2d.compareTo(other: Vector2d): Int {
var cmp = x.compareTo(other.x)
if (cmp == 0) cmp = y.compareTo(y)
return cmp
}
data class Line2d(val a: Vector2d, val b: Vector2d) {
data class Intersection(val intersects: Boolean, val point: KOptional<Vector2d>, val t: KOptional<Double>, val coincides: Boolean, val glances: Boolean) {
companion object {
val EMPTY = Intersection(false, KOptional(), KOptional(), false, false)
}
}
fun difference(): Vector2d {
return b - a
}
fun reverse(): Line2d {
return Line2d(b, a)
}
// original source of this intersection algorithm:
// https://stackoverflow.com/questions/563198/how-do-you-detect-where-two-line-segments-intersect
// article "Intersection of two lines in three-space" by Ronald Goldman, published in Graphics Gems, page 304
fun intersect(other: Line2d, infinite: Boolean = false): Intersection {
val (c, d) = other
val ab = difference()
val cd = other.difference()
val abCross = a.cross(b)
val cdCross = c.cross(d)
val denominator = ab.cross(cd)
val xNumber = abCross * cd.x - cdCross * ab.x
val yNumber = abCross * cd.y - cdCross * ab.y
if (denominator.absoluteValue <= NEAR_ZERO) {
if (xNumber.absoluteValue <= NEAR_ZERO && yNumber.absoluteValue <= NEAR_ZERO) {
val intersects = infinite || (a >= c && a <= d) || (c >= a && c <= b)
var point: Vector2d? = null
var t = 0.0
if (intersects) {
if (infinite) {
point = Vector2d(Double.NEGATIVE_INFINITY, Double.NEGATIVE_INFINITY)
} else {
point = if (a < c) c else a
}
}
if (a < c) {
if (c.x != a.x) {
t = (c.x - a.x) / ab.x
} else {
t = (c.y - a.y) / ab.y
}
} else if (a > d) {
if (d.x != a.x) {
t = (d.x - a.x) / ab.x
} else {
t = (d.y - a.y) / ab.y
}
}
return Intersection(intersects, KOptional.ofNullable(point), KOptional(t), true, intersects)
} else {
return Intersection.EMPTY
}
} else {
val ta = (c - a).cross(cd) / denominator
val tb = (c - a).cross(ab) / denominator
val intersects = infinite || (ta in 0.0 .. 1.0 && tb in 0.0 .. 1.0)
return Intersection(
intersects = intersects,
t = KOptional(ta),
point = KOptional((b - a) * ta + a),
coincides = false,
glances = !infinite && intersects && (ta <= NEAR_ZERO || ta >= NEAR_ONE || tb <= NEAR_ZERO || tb >= NEAR_ONE)
)
}
}
fun project(axis: Vector2d): Double {
val diff = difference()
return ((axis.x - a.x) * diff.x + (axis.y - a.y) * diff.y) / diff.lengthSquared
}
fun distanceTo(other: Vector2d, infinite: Boolean = false): Double {
var proj = project(other)
if (!infinite)
proj = proj.coerceIn(0.0, 1.0)
return (other - a + difference() * proj).length
}
companion object : TypeAdapterFactory {
const val NEAR_ZERO = Double.MIN_VALUE * 2.0
const val NEAR_ONE = 1.0 - NEAR_ZERO
override fun <T : Any> create(gson: Gson, type: TypeToken<T>): TypeAdapter<T>? {
if (type.rawType == Line2d::class.java) {
val pair = gson.getAdapter(TypeToken.getParameterized(Vector2d::class.java, Vector2d::class.java)) as TypeAdapter<Pair<Vector2d, Vector2d>>
return object : TypeAdapter<Line2d>() {
override fun write(out: JsonWriter, value: Line2d?) {
if (value == null)
out.nullValue()
else
pair.write(out, value.a to value.b)
}
override fun read(`in`: JsonReader): Line2d? {
if (`in`.consumeNull())
return null
val (a, b) = pair.read(`in`)
return Line2d(a, b)
}
} as TypeAdapter<T>
}
return null
}
}
}

View File

@ -1,37 +0,0 @@
package ru.dbotthepony.kstarbound.math
import com.google.gson.Gson
import com.google.gson.TypeAdapter
import com.google.gson.stream.JsonReader
import com.google.gson.stream.JsonWriter
import ru.dbotthepony.kommons.vector.Vector2f
import ru.dbotthepony.kstarbound.json.consumeNull
data class LineF(val start: Vector2f, val end: Vector2f) {
class Adapter(gson: Gson) : TypeAdapter<LineF>() {
private val vectors = gson.getAdapter(Vector2f::class.java)
override fun write(out: JsonWriter, value: LineF?) {
if (value == null) {
out.nullValue()
} else {
out.beginArray()
vectors.write(out, value.start)
vectors.write(out, value.end)
out.endArray()
}
}
override fun read(`in`: JsonReader): LineF? {
if (`in`.consumeNull()) {
return null
} else {
`in`.beginArray()
val start = vectors.read(`in`)
val end = vectors.read(`in`)
`in`.endArray()
return LineF(start, end)
}
}
}
}

View File

@ -10,7 +10,7 @@ import ru.dbotthepony.kstarbound.Starbound
import ru.dbotthepony.kstarbound.defs.quest.QuestTemplate
import ru.dbotthepony.kstarbound.lua.NewLuaState
import ru.dbotthepony.kstarbound.util.ItemStack
import ru.dbotthepony.kstarbound.json.set
import ru.dbotthepony.kommons.gson.set
import java.util.UUID
class QuestInstance(

View File

@ -3,6 +3,7 @@ package ru.dbotthepony.kstarbound.server
import org.apache.logging.log4j.LogManager
import ru.dbotthepony.kommons.util.MailboxExecutorService
import ru.dbotthepony.kstarbound.Starbound
import ru.dbotthepony.kstarbound.server.world.ServerUniverse
import ru.dbotthepony.kstarbound.server.world.ServerWorld
import ru.dbotthepony.kstarbound.util.ExecutionSpinner
import java.io.Closeable
@ -26,8 +27,9 @@ sealed class StarboundServer(val root: File) : Closeable {
val serverID = threadCounter.getAndIncrement()
val mailbox = MailboxExecutorService()
val spinner = ExecutionSpinner(mailbox, ::spin, Starbound.TICK_TIME_ADVANCE_NANOS)
val spinner = ExecutionSpinner(mailbox::executeQueuedTasks, ::spin, Starbound.TICK_TIME_ADVANCE_NANOS)
val thread = Thread(spinner, "Starbound Server $serverID")
val universe = ServerUniverse()
val settings = ServerSettings()
val channels = ServerChannels(this)
@ -64,6 +66,7 @@ sealed class StarboundServer(val root: File) : Closeable {
channels.close()
worlds.forEach { it.close() }
universe.close()
close0()
}

View File

@ -2,13 +2,13 @@ package ru.dbotthepony.kstarbound.server.world
import org.apache.logging.log4j.LogManager
import ru.dbotthepony.kommons.arrays.Object2DArray
import ru.dbotthepony.kommons.io.BTreeDB
import ru.dbotthepony.kommons.io.ByteKey
import ru.dbotthepony.kommons.util.KOptional
import ru.dbotthepony.kommons.io.readVarInt
import ru.dbotthepony.kommons.util.CarriedExecutor
import ru.dbotthepony.kommons.vector.Vector2i
import ru.dbotthepony.kstarbound.Starbound
import ru.dbotthepony.kstarbound.io.BTreeDB5
import ru.dbotthepony.kstarbound.json.VersionedJson
import ru.dbotthepony.kstarbound.world.CHUNK_SIZE
import ru.dbotthepony.kstarbound.world.ChunkPos
@ -25,7 +25,7 @@ import java.util.function.Supplier
import java.util.zip.Inflater
import java.util.zip.InflaterInputStream
class LegacyChunkSource(val db: BTreeDB<ByteKey, ByteArray>) : IChunkSource {
class LegacyChunkSource(val db: BTreeDB5) : IChunkSource {
private val carrier = CarriedExecutor(Starbound.STORAGE_IO_POOL)
override fun getTiles(pos: ChunkPos): CompletableFuture<KOptional<Object2DArray<out AbstractCell>>> {

View File

@ -0,0 +1,170 @@
package ru.dbotthepony.kstarbound.server.world
import com.github.benmanes.caffeine.cache.Cache
import com.github.benmanes.caffeine.cache.Caffeine
import com.github.benmanes.caffeine.cache.Scheduler
import com.google.gson.stream.JsonReader
import it.unimi.dsi.fastutil.io.FastByteArrayInputStream
import it.unimi.dsi.fastutil.objects.ObjectArrayList
import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet
import ru.dbotthepony.kommons.collect.chainOptionalFutures
import ru.dbotthepony.kommons.gson.get
import ru.dbotthepony.kommons.io.BTreeDB6
import ru.dbotthepony.kommons.util.AABBi
import ru.dbotthepony.kommons.util.IStruct2i
import ru.dbotthepony.kommons.util.KOptional
import ru.dbotthepony.kommons.vector.Vector2i
import ru.dbotthepony.kstarbound.Starbound
import ru.dbotthepony.kstarbound.defs.CelestialBaseInformation
import ru.dbotthepony.kstarbound.defs.CelestialGenerationInformation
import ru.dbotthepony.kstarbound.defs.CelestialNames
import ru.dbotthepony.kstarbound.fromJson
import ru.dbotthepony.kstarbound.io.BTreeDB5
import ru.dbotthepony.kstarbound.util.random.staticRandom64
import ru.dbotthepony.kstarbound.world.CoordinateMapper
import ru.dbotthepony.kstarbound.world.Universe
import ru.dbotthepony.kstarbound.world.UniversePos
import ru.dbotthepony.kstarbound.world.positiveModulo
import java.io.Closeable
import java.io.File
import java.io.InputStreamReader
import java.time.Duration
import java.util.*
import java.util.concurrent.CompletableFuture
import kotlin.collections.ArrayList
class ServerUniverse private constructor(marker: Nothing?) : Universe(), Closeable {
constructor() : this(null) {
sources.add(NativeUniverseSource(null, this).generator)
}
constructor(folder: File) : this(null) {
val nativeFile = File(folder, "universe.kchunks")
val legacyFile = File(folder, "universe.chunks")
val native = if (!nativeFile.exists()) {
NativeUniverseSource(BTreeDB6.create(nativeFile, sync = false), this)
} else {
NativeUniverseSource(BTreeDB6(nativeFile, sync = false), this)
}
val legacy = if (legacyFile.exists()) {
LegacyUniverseSource(BTreeDB5(legacyFile))
} else {
null
}
sources.add(native.reader)
if (legacy != null) sources.add(legacy)
sources.add(native.generator)
closeables.add(native)
if (legacy != null) closeables.add(legacy)
}
override val baseInformation: CelestialBaseInformation
val generationInformation: CelestialGenerationInformation
val celestialNames: CelestialNames
private val sources = ArrayList<UniverseSource>()
private val closeables = ArrayList<Closeable>()
override fun name(pos: UniversePos): CompletableFuture<KOptional<String>> {
return getChunk(pos).thenApply {
it.flatMap { it.parameters(pos) }.map { it.name }
}
}
override fun scanSystems(region: AABBi, includedTypes: Set<String>?): CompletableFuture<List<UniversePos>> {
val copy = if (includedTypes != null) ObjectOpenHashSet(includedTypes) else null
val futures = ArrayList<CompletableFuture<List<UniversePos>>>()
for (pos in chunkPositions(region)) {
val f = getChunk(pos).thenApply {
it.map<List<UniversePos>> {
val result = ArrayList<UniversePos>()
if (copy == null) {
for (system in it.systems.keys) {
result.add(UniversePos(system))
}
} else {
for ((system, params) in it.systems) {
if (params.parameters.parameters.get("typeName", "") in copy) {
result.add(UniversePos(system))
}
}
}
result
}.orElse(listOf())
}
futures.add(f)
}
return CompletableFuture.allOf(*futures.toTypedArray())
.thenApply { futures.stream().flatMap { it.get().stream() }.toList() }
}
override fun scanConstellationLines(region: AABBi): CompletableFuture<List<Pair<Vector2i, Vector2i>>> {
val futures = ArrayList<CompletableFuture<List<Pair<Vector2i, Vector2i>>>>()
for (pos in chunkPositions(region)) {
val f = getChunk(pos).thenApply {
it.map<List<Pair<Vector2i, Vector2i>>> { ObjectArrayList(it.constellations) }.orElse(listOf())
}
futures.add(f)
}
return CompletableFuture.allOf(*futures.toTypedArray())
.thenApply { futures.stream().flatMap { it.get().stream() }.toList() }
}
override fun scanRegionFullyLoaded(region: AABBi): Boolean {
return true
}
override fun close() {
closeables.forEach { it.close() }
}
// Edge case: we load so many chunks AND try to load same chunk twice that it gets loaded/generated twice
// shouldn't cause actual issues though
private val chunkCache: Cache<Vector2i, CompletableFuture<KOptional<UniverseChunk>>> = Caffeine
.newBuilder()
.expireAfterAccess(Duration.ofMinutes(10L))
.maximumSize(1024L)
.softValues()
.scheduler(Scheduler.systemScheduler())
.build()
fun getChunk(pos: UniversePos): CompletableFuture<KOptional<UniverseChunk>> {
return getChunk(world2chunk(Vector2i(pos.location)))
}
fun getChunk(pos: Vector2i): CompletableFuture<KOptional<UniverseChunk>> {
return chunkCache.get(pos) { p -> chainOptionalFutures(sources) { it.getChunk(p) } }
}
init {
val celestial = Starbound.read("/celestial.config")
val stream0 = JsonReader(InputStreamReader(FastByteArrayInputStream(celestial.array())))
val stream1 = JsonReader(InputStreamReader(FastByteArrayInputStream(celestial.array())))
stream0.isLenient = true
stream1.isLenient = true
baseInformation = Starbound.gson.fromJson(stream0)!!
generationInformation = Starbound.gson.fromJson(stream1)!!
if (!generationInformation.systemTypePerlin.isInitialized)
generationInformation.systemTypePerlin.init(staticRandom64("SystemTypePerlin"))
celestialNames = Starbound.gson.fromJson(Starbound.jsonReader("/celestial/names.config"))!!
}
override val region: AABBi = AABBi(Vector2i(baseInformation.xyCoordRange.x, baseInformation.xyCoordRange.x), Vector2i(baseInformation.xyCoordRange.y, baseInformation.xyCoordRange.y))
}

View File

@ -70,7 +70,7 @@ class ServerWorld(
}
}
val spinner = ExecutionSpinner(mailbox, ::spin, Starbound.TICK_TIME_ADVANCE_NANOS)
val spinner = ExecutionSpinner(mailbox::executeQueuedTasks, ::spin, Starbound.TICK_TIME_ADVANCE_NANOS)
val thread = Thread(spinner, "Starbound Server World $seed")
val ticketListLock = ReentrantLock()

View File

@ -0,0 +1,194 @@
package ru.dbotthepony.kstarbound.server.world
import com.google.gson.Gson
import com.google.gson.JsonArray
import com.google.gson.JsonObject
import com.google.gson.TypeAdapter
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.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.CelestialParameters
import ru.dbotthepony.kstarbound.defs.CelestialPlanet
import ru.dbotthepony.kstarbound.json.pairAdapter
import ru.dbotthepony.kstarbound.json.pairListAdapter
import ru.dbotthepony.kstarbound.json.pairSetAdapter
import ru.dbotthepony.kstarbound.world.UniversePos
class UniverseChunk(var chunkPos: Vector2i = Vector2i.ZERO) {
data class System(val parameters: CelestialParameters, val planets: Int2ObjectMap<CelestialPlanet>)
val systems = Object2ObjectOpenHashMap<Vector3i, System>()
val constellations = ObjectOpenHashSet<Pair<Vector2i, Vector2i>>()
fun parameters(coordinate: UniversePos): KOptional<CelestialParameters> {
val system = systems[coordinate.location] ?: return KOptional()
if (coordinate.isSystem)
return KOptional(system.parameters)
else if (coordinate.isPlanet)
return KOptional.ofNullable(system.planets[coordinate.planetOrbit]?.parameters)
else if (coordinate.isSatellite)
return KOptional.ofNullable(system.planets[coordinate.planetOrbit]?.satellites?.get(coordinate.satelliteOrbit))
else
throw RuntimeException("unreachable code")
}
class Adapter(gson: Gson) : TypeAdapter<UniverseChunk>() {
private val vectors = gson.getAdapter(Vector2i::class.java)
private val vectors3 = gson.getAdapter(Vector3i::class.java)
private val objects = gson.getAdapter(JsonObject::class.java)
private val systems = gson.pairListAdapter<Vector3i, CelestialParameters>()
private val params = gson.getAdapter(CelestialParameters::class.java)
private val lines = gson.pairAdapter<Vector2i, Vector2i>()
override fun write(out: JsonWriter, value: UniverseChunk?) {
if (value == null)
out.nullValue()
else {
out.beginObject()
out.name("chunkIndex")
vectors.write(out, value.chunkPos)
out.name("systemParameters")
out.beginArray()
for (system in value.systems) {
out.beginArray()
vectors3.write(out, system.key)
params.write(out, system.value.parameters)
out.endArray()
}
out.endArray()
out.name("systemObjects")
out.beginArray()
for (system in value.systems) {
out.beginArray()
vectors3.write(out, system.key)
out.beginArray()
for ((orbit, planet) in system.value.planets) {
out.beginArray()
out.value(orbit)
out.beginObject()
out.name("parameters")
params.write(out, planet.parameters)
out.name("satellites")
out.beginArray()
for ((satelliteOrbit, satellite) in planet.satellites) {
out.beginArray()
out.value(satelliteOrbit)
params.write(out, satellite)
out.endArray()
}
out.endArray()
out.endObject()
out.endArray()
}
out.endArray()
out.endArray()
}
out.endArray()
out.name("constellations")
out.beginArray()
out.beginArray()
for (pair in value.constellations) {
lines.write(out, pair)
}
out.endArray()
out.endArray()
out.endObject()
}
}
override fun read(`in`: JsonReader): UniverseChunk? {
if (`in`.consumeNull())
return null
val read = objects.read(`in`)
val chunkPos = read.get("chunkIndex", vectors)
val chunk = UniverseChunk(chunkPos)
// WARNING: Original game engine stores constellations as List<List<Line>>
// We, on the other hand, don't care and store it as List<Line>, since constellations
// are just pairs of system coordinates.
// List<List<Line>> seems to be a leftover from when constellations were supposed to be
// something bigger than just pairs of coordinates, or special processing were required for them.
// (since original game only ever generate a single constellation, or none at all).
// To avoid creating two separate chunk formats, we will save/load constellations as nested list too.
for (sublist in read.getArray("constellations")) {
for (constellation in sublist.asJsonArray) {
chunk.constellations.add(lines.fromJsonTree(constellation))
}
}
read.get("systemParameters", systems).forEach {
chunk.systems[it.first] = System(it.second, Int2ObjectOpenHashMap())
}
// [[pos, [[orbit, {data}]]]]
val systemObjects = read.getArray("systemObjects")
for (elem in systemObjects) {
elem as JsonArray
val pos = vectors3.fromJsonTree(elem[0])
val system = chunk.systems[pos] ?: continue
val planets = elem[1] as JsonArray
for (planetPair in planets) {
planetPair as JsonArray
val orbit = planetPair[0].asInt
val params = planetPair[1] as JsonObject
val planet = CelestialPlanet(this.params.fromJsonTree(params["parameters"]), Int2ObjectOpenHashMap())
val satellitesPairs = params["satellites"] as JsonArray
for (satellitePair in satellitesPairs) {
satellitePair as JsonArray
val satelliteOrbit = satellitePair[0].asInt
val satelliteData = this.params.fromJsonTree(satellitePair[1])
planet.satellites[satelliteOrbit] = satelliteData
}
system.planets[orbit] = planet
}
}
return chunk
}
}
companion object : TypeAdapterFactory {
override fun <T : Any> create(gson: Gson, type: TypeToken<T>): TypeAdapter<T>? {
if (type.rawType == UniverseChunk::class.java) {
return Adapter(gson) as TypeAdapter<T>
}
return null
}
}
}

View File

@ -0,0 +1,381 @@
package ru.dbotthepony.kstarbound.server.world
import com.google.gson.JsonObject
import it.unimi.dsi.fastutil.ints.Int2ObjectArrayMap
import it.unimi.dsi.fastutil.io.FastByteArrayInputStream
import it.unimi.dsi.fastutil.io.FastByteArrayOutputStream
import it.unimi.dsi.fastutil.objects.ObjectArrayList
import org.apache.logging.log4j.LogManager
import ru.dbotthepony.kommons.gson.contains
import ru.dbotthepony.kommons.gson.get
import ru.dbotthepony.kommons.gson.set
import ru.dbotthepony.kommons.io.BTreeDB6
import ru.dbotthepony.kommons.io.ByteKey
import ru.dbotthepony.kommons.util.CarriedExecutor
import ru.dbotthepony.kommons.util.IStruct2i
import ru.dbotthepony.kommons.util.KOptional
import ru.dbotthepony.kommons.vector.Vector2i
import ru.dbotthepony.kommons.vector.Vector3i
import ru.dbotthepony.kstarbound.Starbound
import ru.dbotthepony.kstarbound.defs.CelestialParameters
import ru.dbotthepony.kstarbound.defs.CelestialPlanet
import ru.dbotthepony.kstarbound.defs.JsonDriven
import ru.dbotthepony.kstarbound.io.BTreeDB5
import ru.dbotthepony.kstarbound.json.readJsonElement
import ru.dbotthepony.kstarbound.json.writeJsonElement
import ru.dbotthepony.kstarbound.math.Line2d
import ru.dbotthepony.kstarbound.util.paddedNumber
import ru.dbotthepony.kstarbound.util.random.random
import ru.dbotthepony.kstarbound.util.random.staticRandom64
import ru.dbotthepony.kstarbound.world.UniversePos
import java.io.BufferedInputStream
import java.io.BufferedOutputStream
import java.io.Closeable
import java.io.DataInputStream
import java.io.DataOutputStream
import java.util.concurrent.CompletableFuture
import java.util.concurrent.TimeUnit
import java.util.function.Supplier
import java.util.random.RandomGenerator
import java.util.zip.Deflater
import java.util.zip.DeflaterOutputStream
import java.util.zip.InflaterInputStream
sealed class UniverseSource {
abstract fun getChunk(chunkPos: IStruct2i): CompletableFuture<KOptional<UniverseChunk>>
open fun saveChunk(chunkPos: IStruct2i, data: UniverseChunk): Boolean {
return false
}
}
// legacy chunks are indexed by... 8 byte keys?
// quite strange.
private fun key(chunkPos: IStruct2i): ByteKey {
val (x, y) = chunkPos
val bytes = ByteArray(8)
bytes[0] = ((x ushr 24) and 0xFF).toByte()
bytes[1] = ((x ushr 16) and 0xFF).toByte()
bytes[2] = ((x ushr 8) and 0xFF).toByte()
bytes[3] = ((x ushr 0) and 0xFF).toByte()
bytes[4 + 0] = ((y ushr 24) and 0xFF).toByte()
bytes[4 + 1] = ((y ushr 16) and 0xFF).toByte()
bytes[4 + 2] = ((y ushr 8) and 0xFF).toByte()
bytes[4 + 3] = ((y ushr 0) and 0xFF).toByte()
return ByteKey.wrap(bytes)
}
class LegacyUniverseSource(private val db: BTreeDB5) : UniverseSource(), Closeable {
private val carried = CarriedExecutor(Starbound.STORAGE_IO_POOL)
override fun close() {
carried.shutdown()
carried.awaitTermination(Int.MAX_VALUE.toLong(), TimeUnit.MILLISECONDS)
db.close()
}
override fun getChunk(chunkPos: IStruct2i): CompletableFuture<KOptional<UniverseChunk>> {
val key = key(chunkPos)
return CompletableFuture.supplyAsync(Supplier { db.read(key) }, carried).thenApplyAsync {
it.map {
val stream = BufferedInputStream(InflaterInputStream(FastByteArrayInputStream(it)))
UniverseChunk(Vector2i(chunkPos))
}
}
}
}
class NativeUniverseSource(private val db: BTreeDB6?, private val universe: ServerUniverse) : Closeable {
private inner class Reader : UniverseSource() {
override fun getChunk(chunkPos: IStruct2i): CompletableFuture<KOptional<UniverseChunk>> {
if (db == null) return CompletableFuture.completedFuture(KOptional())
val key = key(chunkPos)
return CompletableFuture.supplyAsync(Supplier {
db.read(key)
}, carrier).thenApplyAsync {
it.flatMap {
val data = DataInputStream(BufferedInputStream(InflaterInputStream(FastByteArrayInputStream(it)))).readJsonElement()
val expected = Vector2i(chunkPos)
try {
val chunk = Starbound.gson.fromJson(data, UniverseChunk::class.java)
if (expected == chunk.chunkPos) {
KOptional(chunk)
} else {
throw IllegalArgumentException("Universe chunk at $chunkPos has ${chunk.chunkPos} stored as position!")
}
} catch (err: Throwable) {
LOGGER.error("Error while deserializing universe chunk from disk storage at $chunkPos, it will be regenerated", err)
KOptional()
}
}
}
}
override fun saveChunk(chunkPos: IStruct2i, data: UniverseChunk): Boolean {
storeChunk(chunkPos, data)
return true
}
}
// generator will generate different systems than vanilla
// because of different code flow and ability to use different random number generator
private inner class Generator : UniverseSource() {
override fun getChunk(chunkPos: IStruct2i): CompletableFuture<KOptional<UniverseChunk>> {
val random = random(staticRandom64(chunkPos.component1(), chunkPos.component2(), "ChunkIndexMix"))
val region = universe.chunkRegion(chunkPos)
return CompletableFuture.supplyAsync {
val constellationCandidates = ArrayList<Vector2i>()
val chunk = UniverseChunk(Vector2i(chunkPos))
for (x in region.mins.x until region.maxs.x) {
for (y in region.mins.y until region.maxs.y) {
if (random.nextFloat() < universe.generationInformation.systemProbability) {
val z = random.nextInt(universe.baseInformation.zCoordRange.x, universe.baseInformation.zCoordRange.y)
val pos = Vector3i(x, y, z)
val system = generateSystem(random, pos) ?: continue
chunk.systems[pos] = system
if (
system.parameters.parameters.get("constellationCapable", true) &&
system.parameters.parameters.get("magnitude", 0.0) >= universe.generationInformation.minimumConstellationMagnitude
) {
constellationCandidates.add(Vector2i(x, y))
}
}
}
}
for (pair in generateConstellations(random, constellationCandidates)) {
chunk.constellations.add(pair)
}
storeChunk(chunkPos, chunk)
KOptional(chunk)
}
}
private fun generateSystem(random: RandomGenerator, location: Vector3i): UniverseChunk.System? {
val typeSelector = universe.generationInformation.systemTypePerlin[location.x.toDouble(), location.y.toDouble()]
val type = universe.generationInformation.systemTypeBins
.stream()
.sorted { o1, o2 -> o2.first.compareTo(o1.first) }
.filter { it.first <= typeSelector }
.findFirst().map { it.second }.orElse("")
if (type.isBlank())
return null
val system = universe.generationInformation.systemTypes[type]!!
val systemPos = UniversePos(location)
val systemSeed = random.nextLong()
val prefix = universe.celestialNames.systemPrefixNames.sample(random).orElse("")
val mid = universe.celestialNames.systemNames.sample(random).orElse("missingsystemname $location")
val suffix = universe.celestialNames.systemSuffixNames.sample(random).orElse("")
val systemName = "$prefix $mid $suffix".trim()
.replace("<onedigit>", random.nextInt(0, 10).toString())
.replace("<twodigit>", paddedNumber(random.nextInt(0, 100), 2))
.replace("<threedigit>", paddedNumber(random.nextInt(0, 1000), 3))
.replace("<fourdigit>", paddedNumber(random.nextInt(0, 10000), 4))
val systemParams = CelestialParameters(
systemPos,
systemSeed,
systemName,
JsonDriven.mergeNoCopy(system.baseParameters.deepCopy(), system.variationParameters.random(random))
)
if ("typeName" !in systemParams.parameters) {
systemParams.parameters["typeName"] = system.typeName
}
if ("constellationCapable" !in systemParams.parameters) {
systemParams.parameters["constellationCapable"] = system.constellationCapable
}
val planets = Int2ObjectArrayMap<CelestialPlanet>()
for (planetOrbitIndex in 1 .. universe.baseInformation.planetOrbitalLevels) {
// this looks dumb at first, but then it makes sense
// in celestial.config, you define orbital region, where planets
// of only specific type appear.
val systemOrbitRegion = system.orbitRegions
.stream()
.filter { planetOrbitIndex in it.orbitRange.x .. it.orbitRange.y }
.findFirst().orElse(null) ?: continue
if (systemOrbitRegion.bodyProbability > random.nextDouble()) continue
val planetaryTypeO = systemOrbitRegion.planetaryTypes.sample(random).flatMap { KOptional.ofNullable(universe.generationInformation.planetaryTypes[it]) }
if (!planetaryTypeO.isPresent) continue
val planetaryType = planetaryTypeO.value
val planetCoordinate = UniversePos(location, planetOrbitIndex)
val planetSeed = random.nextLong()
val planetName = "$systemName ${universe.celestialNames.planetarySuffixes[planetOrbitIndex]}"
val planetParams = CelestialParameters(
planetCoordinate,
planetSeed,
planetName,
JsonDriven.mergeNoCopy(planetaryType.baseParameters.deepCopy(), planetaryType.variationParameters.random(random))
)
val satellites = Int2ObjectArrayMap<CelestialParameters>()
val maxSatelliteCount = planetaryType.maxSatelliteCount ?: universe.baseInformation.satelliteOrbitalLevels
if (maxSatelliteCount > 0) {
for (satelliteOrbitIndex in 1 .. universe.baseInformation.satelliteOrbitalLevels) {
if (random.nextDouble() < planetaryType.satelliteProbability) {
val satelliteTypeO = systemOrbitRegion.satelliteTypes.sample(random).flatMap { KOptional.ofNullable(universe.generationInformation.satelliteTypes[it]) }
if (!satelliteTypeO.isPresent) continue
val satelliteType = satelliteTypeO.value
val satelliteSeed = random.nextLong()
val satelliteName = "$planetName ${universe.celestialNames.satelliteSuffixes[satellites.size]}"
val satelliteCoordinate = UniversePos(location, planetOrbitIndex, satelliteOrbitIndex)
val merge = JsonObject()
JsonDriven.mergeNoCopy(merge, satelliteType.baseParameters)
JsonDriven.mergeNoCopy(merge, satelliteType.variationParameters.random(random))
if (systemOrbitRegion.regionName in satelliteType.orbitParameters) {
JsonDriven.mergeNoCopy(merge, satelliteType.orbitParameters[systemOrbitRegion.regionName]!!.random(random))
}
satellites[satelliteOrbitIndex] = CelestialParameters(
satelliteCoordinate,
satelliteSeed,
satelliteName,
merge
)
if (satellites.size >= maxSatelliteCount)
break
}
}
}
planets[planetOrbitIndex] = CelestialPlanet(planetParams, satellites)
}
return UniverseChunk.System(systemParams, planets)
}
private fun generateConstellations(random: RandomGenerator, candidates: List<Vector2i>): List<Pair<Vector2i, Vector2i>> {
if (candidates.size <= 2 || random.nextDouble() > universe.generationInformation.constellationProbability) return listOf()
val constellations = ArrayList<Pair<Vector2i, Vector2i>>()
val constellationPoints = ObjectArrayList<Vector2i>()
val constellationLines = ArrayList<Line2d>()
val target = random.nextInt(universe.generationInformation.constellationLineCountRange.x, universe.generationInformation.constellationLineCountRange.y)
var tries = 0
while (constellationLines.size < target && ++tries < universe.generationInformation.constellationMaxTries) {
val start = if (constellationPoints.isEmpty)
candidates.random(random)
else
constellationPoints.random(random)
val end = candidates.random(random)
if (start == end) continue
val proposed = Line2d(start.toDoubleVector(), end.toDoubleVector())
val proposedReversed = proposed.reverse()
if (proposed in constellationLines || proposedReversed in constellationLines) continue
if (start.distance(end) !in universe.generationInformation.minimumConstellationLineLength .. universe.generationInformation.maximumConstellationLineLength) continue
var valid = true
for (existingLine in constellationLines) {
val intersection = proposed.intersect(existingLine)
if (
intersection.intersects &&
intersection.point.get() != proposed.a &&
intersection.point.get() != proposed.b
) {
valid = false
break
}
if (
proposed != existingLine &&
proposed.distanceTo(existingLine.a) < universe.generationInformation.minimumConstellationLineCloseness
) {
valid = false
break
}
if (
proposed != existingLine.reverse() &&
proposed.distanceTo(existingLine.b) < universe.generationInformation.minimumConstellationLineCloseness
) {
valid = false
break
}
}
if (valid) {
// TODO: Original engine generates "single" constellation
// TODO: out of multiple (probably disconnected) point pairs.
// TODO: Should we do the same? It just doesn't seem to make much sense
// Side effect: It is now possible to have chunks where only two stars are connected (one line)
// Original game engine requires at least two lines to be present to form a constellation
constellations.add(start to end)
constellationLines.add(proposed)
constellationPoints.add(start)
constellationPoints.add(end)
}
}
return constellations
}
}
private fun storeChunk(chunkPos: IStruct2i, chunk: UniverseChunk) {
if (db == null) return
val key = key(chunkPos)
try {
val data = Starbound.gson.toJsonTree(chunk)
val binaryData = FastByteArrayOutputStream()
val stream = DataOutputStream(BufferedOutputStream(DeflaterOutputStream(binaryData, Deflater(2))))
stream.writeJsonElement(data)
stream.close()
carrier.execute {
db.write(key, binaryData.array, 0, binaryData.length)
}
} catch (err: Throwable) {
LOGGER.error("Error while saving universe chunk at $chunkPos, it will not persist", err)
}
}
private val carrier = CarriedExecutor(Starbound.STORAGE_IO_POOL)
val reader: UniverseSource = Reader()
val generator: UniverseSource = Generator()
override fun close() {
carrier.shutdown()
carrier.awaitTermination(Int.MAX_VALUE.toLong(), TimeUnit.MILLISECONDS)
db?.close()
}
companion object {
private val LOGGER = LogManager.getLogger()
}
}

View File

@ -8,7 +8,7 @@ import ru.dbotthepony.kstarbound.WindowsBindings
import java.util.concurrent.locks.LockSupport
import java.util.function.BooleanSupplier
class ExecutionSpinner(private val executor: MailboxExecutorService, private val spinner: BooleanSupplier, private val timeBetweenFrames: Long) : Runnable {
class ExecutionSpinner(private val waiter: Runnable, private val spinner: BooleanSupplier, private val timeBetweenFrames: Long) : Runnable {
init {
Companion
}
@ -54,7 +54,7 @@ class ExecutionSpinner(private val executor: MailboxExecutorService, private val
var diff = timeUntilNextFrame()
while (diff > 0L) {
executor.executeQueuedTasks()
waiter.run()
diff = timeUntilNextFrame()
if (diff >= SYSTEM_SCHEDULER_RESOLUTION * 2L)

View File

@ -12,7 +12,7 @@ import ru.dbotthepony.kstarbound.Registry
import ru.dbotthepony.kstarbound.Starbound
import ru.dbotthepony.kstarbound.defs.item.ItemDescriptor
import ru.dbotthepony.kstarbound.defs.item.api.IItemDefinition
import ru.dbotthepony.kstarbound.json.consumeNull
import ru.dbotthepony.kommons.gson.consumeNull
import ru.dbotthepony.kstarbound.lua.from
class ItemStack private constructor(item: Registry.Entry<IItemDefinition>?, count: Long, val parameters: JsonObject, marker: Unit) {

View File

@ -1,18 +1,10 @@
package ru.dbotthepony.kstarbound.util
import com.google.common.collect.ImmutableList
import com.google.common.collect.ImmutableMap
import com.google.common.collect.ImmutableSet
import com.google.gson.JsonArray
import com.google.gson.JsonElement
import com.google.gson.JsonObject
import ru.dbotthepony.kommons.util.KOptional
import ru.dbotthepony.kstarbound.Starbound
import java.lang.ref.Reference
import java.util.*
import java.util.concurrent.CompletableFuture
import java.util.function.Consumer
import java.util.stream.Stream
fun String.sbIntern(): String {
return Starbound.STRINGS.intern(this)
@ -72,3 +64,15 @@ fun UUID.toStarboundString(): String {
return builder.toString()
}
fun paddedNumber(number: Int, digits: Int): String {
val str = number.toString()
if (str.length > digits) {
return str.substring(0, digits)
} else if (str.length == digits) {
return str
} else {
return "0".repeat(digits - str.length) + str
}
}

View File

@ -0,0 +1,238 @@
package ru.dbotthepony.kstarbound.util.random
import com.google.gson.Gson
import com.google.gson.TypeAdapter
import com.google.gson.TypeAdapterFactory
import com.google.gson.reflect.TypeToken
import com.google.gson.stream.JsonReader
import com.google.gson.stream.JsonWriter
import ru.dbotthepony.kommons.arrays.Double2DArray
import ru.dbotthepony.kommons.math.linearInterpolation
import ru.dbotthepony.kstarbound.defs.PerlinNoiseParameters
import ru.dbotthepony.kommons.gson.consumeNull
import kotlin.math.floor
import kotlin.math.sqrt
/**
* Base class for noise generator.
*
* Once [init]ialized, generator is thread-safe, and can be sampled
* concurrently by any amount of threads.
*/
abstract class AbstractPerlinNoise(val parameters: PerlinNoiseParameters) {
abstract operator fun get(x: Double): Double
abstract operator fun get(x: Double, y: Double): Double
abstract operator fun get(x: Double, y: Double, z: Double): Double
var isInitialized = false
private set
init {
if (parameters.seed != null) {
init(parameters.seed)
}
}
val scaleD = parameters.scale.toDouble()
protected data class Setup(val b0: Int, val b1: Int, val r0: Double, val r1: Double)
protected val p = IntArray(parameters.scale * 2 + 2)
protected val g1 = DoubleArray(parameters.scale * 2 + 2)
protected val g2 = Double2DArray.allocate(parameters.scale * 2 + 2, 2)
protected val g3 = Double2DArray.allocate(parameters.scale * 2 + 2, 3)
fun init(seed: Long) {
isInitialized = true
p.fill(0)
g1.fill(0.0)
g2.fill(0.0)
g3.fill(0.0)
val random = random(seed)
for (i in 0 until parameters.scale) {
p[i] = i
g1[i] = random.nextInt(-parameters.scale, parameters.scale) / scaleD
g2[i, 0] = random.nextInt(-parameters.scale, parameters.scale) / scaleD
g2[i, 1] = random.nextInt(-parameters.scale, parameters.scale) / scaleD
g3[i, 0] = random.nextInt(-parameters.scale, parameters.scale) / scaleD
g3[i, 1] = random.nextInt(-parameters.scale, parameters.scale) / scaleD
g3[i, 2] = random.nextInt(-parameters.scale, parameters.scale) / scaleD
val l2 = sqrt(g2[i, 0] * g2[i, 0] + g2[i, 1] * g2[i, 1])
val l3 = sqrt(g3[i, 0] * g3[i, 0] + g3[i, 1] * g3[i, 1] + g3[i, 2] * g3[i, 2])
if (l2 == 0.0) {
g2[i, 0] = 1.0
g2[i, 1] = 0.0
} else {
g2[i, 0] = g2[i, 0] / l2
g2[i, 1] = g2[i, 1] / l2
}
if (l3 == 0.0) {
g3[i, 0] = 1.0
g3[i, 1] = 0.0
g3[i, 2] = 0.0
} else {
g3[i, 0] = g3[i, 0] / l3
g3[i, 1] = g3[i, 1] / l3
g3[i, 2] = g3[i, 2] / l3
}
}
for (i in parameters.scale downTo 1) {
val k = p[i]
val j = random.nextInt(0, parameters.scale - 1)
p[i] = p[j]
p[j] = k
}
for (i in 0 until parameters.scale + 2) {
p[parameters.scale + i] = p[i]
g1[parameters.scale + i] = g1[i]
g2[parameters.scale + i, 0] = g2[i, 0]
g2[parameters.scale + i, 1] = g2[i, 1]
g3[parameters.scale + i, 0] = g3[i, 0]
g3[parameters.scale + i, 1] = g3[i, 1]
g3[parameters.scale + i, 2] = g3[i, 2]
}
}
protected fun curve(value: Double): Double {
return value * value * (3.0 - 2.0 * value)
}
protected fun setup(value: Double): Setup {
val iv: Int = floor(value).toInt()
val fv: Double = value - iv
val b0 = iv and (parameters.scale - 1)
val b1 = (iv + 1) and (parameters.scale - 1)
val r1 = fv - 1.0
return Setup(b0, b1, fv, r1)
}
protected fun at2(x: Double, y: Double, rx: Double, ry: Double): Double {
return rx * x + ry * y
}
protected fun at3(x: Double, y: Double, z: Double, rx: Double, ry: Double, rz: Double): Double {
return rx * x + ry * y + rz * z
}
protected fun noise1(x: Double): Double {
val (bx0, bx1, rx0, rx1) = setup(x)
val sx = curve(rx0)
val u = rx0 * g1[p[bx0]]
val v = rx1 * g1[p[bx1]]
return linearInterpolation(sx, u, v)
}
protected fun noise2(x: Double, y: Double): Double {
val (bx0, bx1, rx0, rx1) = setup(x)
val (by0, by1, ry0, ry1) = setup(y)
val i = p[bx0]
val j = p[bx1]
val b00 = p[i + by0]
val b10 = p[j + by0]
val b01 = p[i + by1]
val b11 = p[j + by1]
val sx = curve(rx0)
val sy = curve(ry0)
var u = at2(g2[b00, 0], g2[b00, 1], rx0, ry0)
var v = at2(g2[b10, 0], g2[b10, 1], rx1, ry0)
val a = linearInterpolation(sx, u, v)
u = at2(g2[b01, 0], g2[b01, 1], rx0, ry1)
v = at2(g2[b11, 0], g2[b11, 1], rx1, ry1)
val b = linearInterpolation(sx, u, v)
return linearInterpolation(sy, a, b)
}
protected fun noise3(x: Double, y: Double, z: Double): Double {
val (bx0, bx1, rx0, rx1) = setup(x)
val (by0, by1, ry0, ry1) = setup(y)
val (bz0, bz1, rz0, rz1) = setup(z)
val i = p[bx0];
val j = p[bx1];
val b00 = p[i + by0];
val b10 = p[j + by0];
val b01 = p[i + by1];
val b11 = p[j + by1];
val sx = curve(rx0);
val sy = curve(ry0);
val sz = curve(rz0);
var u = at3(g3[b00 + bz0, 0], g3[b00 + bz0, 1], g3[b00 + bz0, 2], rx0, ry0, rz0)
var v = at3(g3[b10 + bz0, 0], g3[b10 + bz0, 1], g3[b10 + bz0, 2], rx1, ry0, rz0)
var a = linearInterpolation(sx, u, v)
u = at3(g3[b01 + bz0, 0], g3[b01 + bz0, 1], g3[b01 + bz0, 2], rx0, ry1, rz0)
v = at3(g3[b11 + bz0, 0], g3[b11 + bz0, 1], g3[b11 + bz0, 2], rx1, ry1, rz0)
var b = linearInterpolation(sx, u, v)
val c = linearInterpolation(sy, a, b)
u = at3(g3[b00 + bz1, 0], g3[b00 + bz1, 1], g3[b00 + bz1, 2], rx0, ry0, rz1)
v = at3(g3[b10 + bz1, 0], g3[b10 + bz1, 1], g3[b10 + bz1, 2], rx1, ry0, rz1)
a = linearInterpolation(sx, u, v)
u = at3(g3[b01 + bz1, 0], g3[b01 + bz1, 1], g3[b01 + bz1, 2], rx0, ry1, rz1)
v = at3(g3[b11 + bz1, 0], g3[b11 + bz1, 1], g3[b11 + bz1, 2], rx1, ry1, rz1)
b = linearInterpolation(sx, u, v)
val d = linearInterpolation(sy, a, b)
return linearInterpolation(sz, c, d)
}
companion object : TypeAdapterFactory {
fun of(parameters: PerlinNoiseParameters): AbstractPerlinNoise {
return when (parameters.type) {
PerlinNoiseParameters.Type.PERLIN -> PerlinNoise(parameters)
PerlinNoiseParameters.Type.BILLOW -> BillowNoise(parameters)
PerlinNoiseParameters.Type.RIDGED_MULTI -> RidgedNoise(parameters)
}
}
override fun <T : Any> create(gson: Gson, type: TypeToken<T>): TypeAdapter<T>? {
if (type.rawType == AbstractPerlinNoise::class.java) {
return object : TypeAdapter<AbstractPerlinNoise>() {
private val parent = gson.getAdapter(PerlinNoiseParameters::class.java)
override fun write(out: JsonWriter, value: AbstractPerlinNoise?) {
return parent.write(out, value?.parameters)
}
override fun read(`in`: JsonReader): AbstractPerlinNoise? {
if (`in`.consumeNull())
return null
return of(parent.read(`in`)!!)
}
} as TypeAdapter<T>
}
return null
}
}
}

View File

@ -0,0 +1,58 @@
package ru.dbotthepony.kstarbound.util.random
import ru.dbotthepony.kstarbound.defs.PerlinNoiseParameters
import kotlin.math.absoluteValue
class BillowNoise(parameters: PerlinNoiseParameters) : AbstractPerlinNoise(parameters) {
init {
require(parameters.type == PerlinNoiseParameters.Type.BILLOW)
}
override fun get(x: Double): Double {
var sum = 0.0
var p = x * parameters.frequency
var scale = 1.0
for (i in 0 until parameters.octaves) {
sum += (2.0 * noise1(p).absoluteValue - 1.0) / scale
scale *= parameters.alpha
p *= parameters.beta
}
return (sum + 0.5) * parameters.amplitude + parameters.bias
}
override fun get(x: Double, y: Double): Double {
var sum = 0.0
var px = x * parameters.frequency
var py = y * parameters.frequency
var scale = 1.0
for (i in 0 until parameters.octaves) {
sum += (2.0 * noise2(px, py).absoluteValue - 1.0) / scale
scale *= parameters.alpha
px *= parameters.beta
py *= parameters.beta
}
return (sum + 0.5) * parameters.amplitude + parameters.bias
}
override fun get(x: Double, y: Double, z: Double): Double {
var sum = 0.0
var px = x * parameters.frequency
var py = y * parameters.frequency
var pz = z * parameters.frequency
var scale = 1.0
for (i in 0 until parameters.octaves) {
sum += (2.0 * noise3(px, py, pz).absoluteValue - 1.0) / scale
scale *= parameters.alpha
px *= parameters.beta
py *= parameters.beta
pz *= parameters.beta
}
return (sum + 0.5) * parameters.amplitude + parameters.bias
}
}

View File

@ -0,0 +1,71 @@
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
private var dataIndex = 0
var seed: Long = seed
private set
init {
require(windupIterations >= 0) { "Negative amount of windup iterations: $windupIterations" }
init(seed, windupIterations)
}
/**
* re-initialize this MWC generator
*/
fun init(seed: Long, windupIterations: Int = 0) {
this.seed = seed
carry = (seed % MAGIC).toInt()
data[0] = seed.toInt()
data[1] = seed.ushr(32).toInt()
for (i in 2 until data.size) {
data[i] = 69069 * data[i - 2] + 362437
}
dataIndex = data.size - 1
// initial windup
for (i in 0 until windupIterations) {
nextInt()
}
}
fun addEntropy(seed: Long = System.nanoTime()) {
// Same algo as init, but bitwise xor with existing data
carry = ((carry.toLong().xor(seed)) % MAGIC).toInt()
data[0] = data[0].xor(seed.toInt())
data[1] = data[1].xor(seed.ushr(32).xor(seed).toInt())
for (i in 2 until data.size) {
data[i] = data[i].xor(69069 * data[i - 2] + 362437)
}
}
override fun nextLong(): Long {
dataIndex = (dataIndex + 1) % data.size
val t = MAGIC.toLong() * data[dataIndex].toLong() + carry.toLong()
carry = t.ushr(32).toInt()
data[dataIndex] = t.toInt()
return t
}
companion object {
const val MAGIC = 809430660
val GLOBAL: MWCRandom by ThreadLocal.withInitial { MWCRandom() }
}
}

View File

@ -0,0 +1,57 @@
package ru.dbotthepony.kstarbound.util.random
import ru.dbotthepony.kstarbound.defs.PerlinNoiseParameters
class PerlinNoise(parameters: PerlinNoiseParameters) : AbstractPerlinNoise(parameters) {
init {
require(parameters.type == PerlinNoiseParameters.Type.PERLIN)
}
override fun get(x: Double): Double {
var sum = 0.0
var p = x * parameters.frequency
var scale = 1.0
for (i in 0 until parameters.octaves) {
sum += noise1(p) / scale
scale *= parameters.alpha
p *= parameters.beta
}
return sum * parameters.amplitude + parameters.bias
}
override fun get(x: Double, y: Double): Double {
var sum = 0.0
var px = x * parameters.frequency
var py = y * parameters.frequency
var scale = 1.0
for (i in 0 until parameters.octaves) {
sum += noise2(px, py) / scale
scale *= parameters.alpha
px *= parameters.beta
py *= parameters.beta
}
return sum * parameters.amplitude + parameters.bias
}
override fun get(x: Double, y: Double, z: Double): Double {
var sum = 0.0
var px = x * parameters.frequency
var py = y * parameters.frequency
var pz = z * parameters.frequency
var scale = 1.0
for (i in 0 until parameters.octaves) {
sum += noise3(px, py, pz) / scale
scale *= parameters.alpha
px *= parameters.beta
py *= parameters.beta
pz *= parameters.beta
}
return sum * parameters.amplitude + parameters.bias
}
}

View File

@ -0,0 +1,209 @@
package ru.dbotthepony.kstarbound.util.random
import com.google.gson.JsonArray
import com.google.gson.JsonElement
import it.unimi.dsi.fastutil.bytes.ByteConsumer
import ru.dbotthepony.kommons.util.XXHash32
import ru.dbotthepony.kommons.util.XXHash64
import java.util.*
import java.util.random.RandomGenerator
import java.util.stream.IntStream
import kotlin.NoSuchElementException
import kotlin.collections.List
import kotlin.math.ceil
import kotlin.math.floor
import kotlin.math.ln
import kotlin.math.sqrt
/**
* Decouples implementation from semantics.
* Code should construct random generator using this function.
* Replacing generator returned here will affect all random generation code.
*/
fun random(seed: Long = System.nanoTime()): RandomGenerator {
return MWCRandom(seed)
}
private fun toBytes(accept: ByteConsumer, value: Short) {
accept.accept(value.toByte())
accept.accept((value.toInt() ushr 8).toByte())
}
private fun toBytes(accept: ByteConsumer, value: Int) {
accept.accept(value.toByte())
accept.accept((value ushr 8).toByte())
accept.accept((value ushr 16).toByte())
accept.accept((value ushr 24).toByte())
}
private fun toBytes(accept: ByteConsumer, value: Long) {
accept.accept(value.toByte())
accept.accept((value ushr 8).toByte())
accept.accept((value ushr 16).toByte())
accept.accept((value ushr 24).toByte())
accept.accept((value ushr 32).toByte())
accept.accept((value ushr 40).toByte())
accept.accept((value ushr 48).toByte())
accept.accept((value ushr 56).toByte())
}
private fun toBytes(accept: ByteConsumer, value: Double) {
toBytes(accept, value.toBits())
}
private fun toBytes(accept: ByteConsumer, value: Float) {
toBytes(accept, value.toBits())
}
fun staticRandom32(vararg values: Any): Int {
val digest = XXHash32(2938728349.toInt())
for (value in values) {
when (value) {
is String -> digest.update(value.toByteArray())
is Byte -> digest.update(value)
is Boolean -> digest.update(if (value) 1 else 0)
is Short -> toBytes(digest::update, value)
is Int -> toBytes(digest::update, value)
is Long -> toBytes(digest::update, value)
is Double -> toBytes(digest::update, value)
is Float -> toBytes(digest::update, value)
else -> throw IllegalArgumentException("Can't hash value of type ${value::class.qualifiedName}")
}
}
return digest.digestAsInt()
}
fun staticRandom64(vararg values: Any): Long {
val digest = XXHash64(1997293021376312589L)
for (value in values) {
when (value) {
is String -> digest.update(value.toByteArray())
is Byte -> digest.update(value)
is Boolean -> digest.update(if (value) 1 else 0)
is Short -> toBytes(digest::update, value)
is Int -> toBytes(digest::update, value)
is Long -> toBytes(digest::update, value)
is Double -> toBytes(digest::update, value)
is Float -> toBytes(digest::update, value)
else -> throw IllegalArgumentException("Can't hash value of type ${value::class.qualifiedName}")
}
}
return digest.digestAsLong()
}
// normal distribution via Box-Muller
fun RandomGenerator.nextNormalFloat(stddev: Float, mean: Float): Float {
var rand1: Float
var rand2: Float
var distSqr: Float
do {
rand1 = 2f * nextFloat() - 1f
rand2 = 2f * nextFloat() - 1f
distSqr = rand1 * rand1 + rand2 * rand2
} while (distSqr >= 1)
val mapping = sqrt(-2f * ln(distSqr) / distSqr)
return rand1 * mapping * stddev + mean
}
// normal distribution via Box-Muller
fun RandomGenerator.nextNormalDouble(stddev: Double, mean: Double): Double {
var rand1: Double
var rand2: Double
var distSqr: Double
do {
rand1 = 2.0 * nextDouble() - 1.0
rand2 = 2.0 * nextDouble() - 1.0
distSqr = rand1 * rand1 + rand2 * rand2
} while (distSqr >= 1)
val mapping = sqrt(-2.0 * ln(distSqr) / distSqr)
return rand1 * mapping * stddev + mean
}
fun RandomGenerator.stochasticRound(value: Double): Long {
val part = value - floor(value)
if (nextDouble() < part)
return ceil(value).toLong()
else
return floor(value).toLong()
}
fun RandomGenerator.bytes(): IntStream {
// avoid flatMap since that would create lots of heap garbage
val data = IntArray(4)
var index = 0
var populated = false
return IntStream.generate {
if (!populated || index == 4) {
val value = nextInt()
data[0] = value.and(0xFF)
data[1] = value.ushr(8).and(0xFF)
data[2] = value.ushr(16).and(0xFF)
data[3] = value.ushr(24).and(0xFF)
populated = true
index = 0
}
data[index++]
}
}
fun RandomGenerator.nextBytes(buf: ByteArray, offset: Int, length: Int) {
Objects.checkFromIndexSize(offset, buf.size, length)
if (length == 0) return
var offset = offset
bytes().limit(length.toLong()).forEach {
buf[offset++] = it.toByte()
}
}
fun RandomGenerator.nextBytes(length: Int): ByteArray {
if (length == 0) return ByteArray(0)
require(length > 0) { "Invalid length: $length" }
val data = ByteArray(length)
var index = 0
bytes().limit(length.toLong()).forEach {
data[index++] = it.toByte()
}
return data
}
fun <T> Collection<T>.random(random: RandomGenerator): T {
if (isEmpty())
throw NoSuchElementException("List is empty")
return elementAt(random.nextInt(size))
}
fun JsonArray.random(random: RandomGenerator): JsonElement {
if (isEmpty())
throw NoSuchElementException("List is empty")
return elementAt(random.nextInt(size()))
}
fun <T> Collection<T>.random(random: RandomGenerator, default: () -> T): T {
if (isEmpty())
return default.invoke()
return elementAt(random.nextInt(size))
}
fun JsonArray.random(random: RandomGenerator, default: () -> JsonElement): JsonElement {
if (isEmpty())
return default.invoke()
return elementAt(random.nextInt(size()))
}

View File

@ -0,0 +1,82 @@
package ru.dbotthepony.kstarbound.util.random
import ru.dbotthepony.kstarbound.defs.PerlinNoiseParameters
import kotlin.math.absoluteValue
class RidgedNoise(parameters: PerlinNoiseParameters) : AbstractPerlinNoise(parameters) {
init {
require(parameters.type == PerlinNoiseParameters.Type.RIDGED_MULTI)
}
override fun get(x: Double): Double {
var sum = 0.0
var p = x * parameters.frequency
var scale = 1.0
var weight = 1.0
for (i in 0 until parameters.octaves) {
var value = noise1(p)
value = parameters.offset - value.absoluteValue
value *= value
value *= weight
weight = (value * parameters.gain).coerceIn(0.0, 1.0)
sum += value / scale
scale *= parameters.alpha
p *= parameters.beta
}
return ((sum * 1.25) - 1.0) * parameters.amplitude + parameters.bias
}
override fun get(x: Double, y: Double): Double {
var sum = 0.0
var px = x * parameters.frequency
var py = y * parameters.frequency
var scale = 1.0
var weight = 1.0
for (i in 0 until parameters.octaves) {
var value = noise2(px, py)
value = parameters.offset - value.absoluteValue
value *= value
value *= weight
weight = (value * parameters.gain).coerceIn(0.0, 1.0)
sum += value / scale
scale *= parameters.alpha
px *= parameters.beta
py *= parameters.beta
}
return ((sum * 1.25) - 1.0) * parameters.amplitude + parameters.bias
}
override fun get(x: Double, y: Double, z: Double): Double {
var sum = 0.0
var px = x * parameters.frequency
var py = y * parameters.frequency
var pz = z * parameters.frequency
var scale = 1.0
var weight = 1.0
for (i in 0 until parameters.octaves) {
var value = noise3(px, py, pz)
value = parameters.offset - value.absoluteValue
value *= value
value *= weight
weight = (value * parameters.gain).coerceIn(0.0, 1.0)
sum += value / scale
scale *= parameters.alpha
px *= parameters.beta
py *= parameters.beta
pz *= parameters.beta
}
return ((sum * 1.25) - 1.0) * parameters.amplitude + parameters.bias
}
}

View File

@ -0,0 +1,66 @@
package ru.dbotthepony.kstarbound.world
import com.github.benmanes.caffeine.cache.Caffeine
import ru.dbotthepony.kommons.util.AABBi
import ru.dbotthepony.kommons.util.IStruct2i
import ru.dbotthepony.kommons.util.KOptional
import ru.dbotthepony.kommons.vector.Vector2i
import ru.dbotthepony.kstarbound.defs.CelestialBaseInformation
import java.time.Duration
import java.util.concurrent.CompletableFuture
abstract class Universe {
abstract val region: AABBi
abstract val baseInformation: CelestialBaseInformation
abstract fun name(pos: UniversePos): CompletableFuture<KOptional<String>>
/**
* Return all valid system coordinates in the given x/y range. All systems
* 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.
*/
abstract fun scanSystems(region: AABBi, includedTypes: Set<String>? = null): CompletableFuture<List<UniversePos>>
abstract fun scanConstellationLines(region: AABBi): CompletableFuture<List<Pair<Vector2i, Vector2i>>>
/**
* Returns false if part or all of the specified region is not loaded. This
* is only relevant for calls to scanSystems and scanConstellationLines, and
* does not imply that each individual system in the given region is fully
* loaded with all planets moons etc, only that scanSystem and
* scanConstellationLines are not waiting on missing data.
*/
abstract fun scanRegionFullyLoaded(region: AABBi): Boolean
fun chunkPositions(region: AABBi): List<Vector2i> {
if (region.mins == region.maxs)
return emptyList()
val result = ArrayList<Vector2i>()
val mins = world2chunk(region.mins)
val maxs = world2chunk(region.maxs - Vector2i.POSITIVE_XY)
for (x in mins.x .. maxs.x) {
for (y in mins.y .. maxs.y) {
result.add(Vector2i(x, y))
}
}
return result
}
fun world2chunk(pos: IStruct2i): Vector2i {
val (x, y) = pos
return Vector2i(
(x - positiveModulo(x, baseInformation.chunkSize)) / baseInformation.chunkSize,
(y - positiveModulo(y, baseInformation.chunkSize)) / baseInformation.chunkSize)
}
fun chunkRegion(chunkPos: IStruct2i): AABBi {
val mins = Vector2i(chunkPos) * baseInformation.chunkSize
return AABBi(mins, mins + Vector2i.POSITIVE_XY * baseInformation.chunkSize)
}
}

View File

@ -0,0 +1,133 @@
package ru.dbotthepony.kstarbound.world
import com.google.gson.Gson
import com.google.gson.JsonObject
import com.google.gson.JsonSyntaxException
import com.google.gson.TypeAdapter
import com.google.gson.TypeAdapterFactory
import com.google.gson.reflect.TypeToken
import com.google.gson.stream.JsonReader
import com.google.gson.stream.JsonToken
import com.google.gson.stream.JsonWriter
import ru.dbotthepony.kommons.gson.get
import ru.dbotthepony.kommons.vector.Vector3i
import ru.dbotthepony.kommons.gson.consumeNull
data class UniversePos(val location: Vector3i = Vector3i.ZERO, val planetOrbit: Int = 0, val satelliteOrbit: Int = 0) {
val isSystem: Boolean
get() = planetOrbit == 0
val isPlanet: Boolean
get() = planetOrbit != 0 && satelliteOrbit == 0
val isSatellite: Boolean
get() = planetOrbit != 0 && satelliteOrbit == 0
val orbitNumber: Int
get() = if (isSatellite) satelliteOrbit else if (isPlanet) planetOrbit else 0
fun system(): UniversePos {
if (planetOrbit == 0 && satelliteOrbit == 0)
return this
return UniversePos(location)
}
fun planet(): UniversePos {
if (satelliteOrbit == 0)
return this
return UniversePos(location, planetOrbit)
}
fun satellite(): UniversePos {
return this
}
fun parent(): UniversePos {
if (isSatellite)
return planet()
else if (isPlanet)
return system()
else
return this
}
companion object : TypeAdapterFactory {
private val splitter = Regex("[ _:]")
val ZERO = UniversePos()
override fun <T : Any> create(gson: Gson, type: TypeToken<T>): TypeAdapter<T>? {
if (type.rawType == UniversePos::class.java) {
val vectors = gson.getAdapter(Vector3i::class.java)
val objects = gson.getAdapter(JsonObject::class.java)
return object : TypeAdapter<UniversePos>() {
override fun write(out: JsonWriter, value: UniversePos?) {
if (value == null)
out.nullValue()
else {
out.beginObject()
out.name("location")
vectors.write(out, value.location)
out.name("planet")
out.value(value.planetOrbit)
out.name("satellite")
out.value(value.satelliteOrbit)
out.endObject()
}
}
override fun read(`in`: JsonReader): UniversePos? {
if (`in`.consumeNull())
return null
if (`in`.peek() == JsonToken.BEGIN_OBJECT) {
val values = objects.read(`in`)!!
val location = values.get("location", vectors)
val planet = values.get("planet", 0)
val orbit = values.get("orbit", 0)
return UniversePos(location, planet, orbit)
}
if (`in`.peek() == JsonToken.STRING) {
val read = `in`.nextString().trim()
if (read == "" || read.lowercase() == "")
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)
} catch (err: Throwable) {
throw JsonSyntaxException("Error parsing UniversePos from string", err)
}
}
}
throw JsonSyntaxException("Invalid data type for UniversePos: ${`in`.peek()}")
}
} as TypeAdapter<T>
}
return null
}
}
}

View File

@ -20,8 +20,8 @@ import ru.dbotthepony.kstarbound.defs.image.SpriteReference
import ru.dbotthepony.kstarbound.defs.`object`.ObjectDefinition
import ru.dbotthepony.kstarbound.defs.`object`.ObjectOrientation
import ru.dbotthepony.kstarbound.server.world.ServerWorld
import ru.dbotthepony.kstarbound.json.get
import ru.dbotthepony.kstarbound.json.set
import ru.dbotthepony.kommons.gson.get
import ru.dbotthepony.kommons.gson.set
import ru.dbotthepony.kstarbound.world.Side
import ru.dbotthepony.kstarbound.world.LightCalculator
import ru.dbotthepony.kstarbound.world.PIXELS_IN_STARBOUND_UNITf

View File

@ -17,7 +17,7 @@ import ru.dbotthepony.kommons.util.AABB
import ru.dbotthepony.kommons.vector.Vector2d
import ru.dbotthepony.kstarbound.client.StarboundClient
import ru.dbotthepony.kstarbound.client.gl.vertex.GeometryType
import ru.dbotthepony.kstarbound.json.consumeNull
import ru.dbotthepony.kommons.gson.consumeNull
import ru.dbotthepony.kstarbound.json.listAdapter
import kotlin.math.absoluteValue
import kotlin.math.cos