We welcome the Universe
This commit is contained in:
parent
a06ed42268
commit
73cf5f596c
@ -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
|
||||
|
@ -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")
|
||||
|
@ -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
|
||||
|
@ -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 {
|
||||
|
@ -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 {
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
@ -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)
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
|
||||
/**
|
||||
|
@ -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]
|
||||
|
||||
|
@ -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?) {
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
@ -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"),
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
||||
|
@ -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 {
|
||||
|
@ -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
|
||||
|
@ -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,
|
||||
|
@ -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
|
||||
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
|
@ -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) {
|
||||
|
@ -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 {
|
||||
|
@ -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]}")
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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
|
||||
}
|
142
src/main/kotlin/ru/dbotthepony/kstarbound/math/Line2d.kt
Normal file
142
src/main/kotlin/ru/dbotthepony/kstarbound/math/Line2d.kt
Normal 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
|
||||
}
|
||||
}
|
||||
}
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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(
|
||||
|
@ -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()
|
||||
}
|
||||
|
||||
|
@ -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>>> {
|
||||
|
@ -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))
|
||||
|
||||
}
|
@ -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()
|
||||
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
@ -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()
|
||||
}
|
||||
}
|
@ -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)
|
||||
|
@ -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) {
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
@ -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
|
||||
}
|
||||
}
|
@ -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() }
|
||||
}
|
||||
}
|
@ -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
|
||||
}
|
||||
}
|
@ -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()))
|
||||
}
|
@ -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
|
||||
}
|
||||
}
|
66
src/main/kotlin/ru/dbotthepony/kstarbound/world/Universe.kt
Normal file
66
src/main/kotlin/ru/dbotthepony/kstarbound/world/Universe.kt
Normal 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)
|
||||
}
|
||||
}
|
133
src/main/kotlin/ru/dbotthepony/kstarbound/world/UniversePos.kt
Normal file
133
src/main/kotlin/ru/dbotthepony/kstarbound/world/UniversePos.kt
Normal 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
|
||||
}
|
||||
}
|
||||
}
|
@ -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
|
||||
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user