Make worlds be capable of loading from disk, and capable of sending to legacy clients

This commit is contained in:
DBotThePony 2024-03-28 21:43:47 +07:00
parent 6df7710dc2
commit 0c60dd6a02
Signed by: DBot
GPG Key ID: DCC23B5715498507
27 changed files with 400 additions and 142 deletions

View File

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

View File

@ -40,6 +40,7 @@ import ru.dbotthepony.kstarbound.defs.world.VisitableWorldParametersType
import ru.dbotthepony.kstarbound.defs.world.BiomePlaceables
import ru.dbotthepony.kstarbound.defs.world.BiomePlacementDistributionType
import ru.dbotthepony.kstarbound.defs.world.BiomePlacementItemType
import ru.dbotthepony.kstarbound.defs.world.WorldLayout
import ru.dbotthepony.kstarbound.world.terrain.TerrainSelectorType
import ru.dbotthepony.kstarbound.io.*
import ru.dbotthepony.kstarbound.json.factory.MapsTypeAdapterFactory
@ -58,6 +59,7 @@ import ru.dbotthepony.kstarbound.json.factory.SingletonTypeAdapterFactory
import ru.dbotthepony.kstarbound.math.*
import ru.dbotthepony.kstarbound.server.world.UniverseChunk
import ru.dbotthepony.kstarbound.item.ItemStack
import ru.dbotthepony.kstarbound.json.NativeLegacy
import ru.dbotthepony.kstarbound.util.Directives
import ru.dbotthepony.kstarbound.util.ExceptionLogger
import ru.dbotthepony.kstarbound.util.SBPattern
@ -155,8 +157,30 @@ object Starbound : ISBFileLocator {
@JvmField
val STRINGS: Interner<String> = interner(5)
// immeasurably lazy and fragile solution
var IS_WRITING_LEGACY_JSON: Boolean by ThreadLocal.withInitial { false }
private set
fun writeLegacyJson(data: Any): JsonElement {
try {
IS_WRITING_LEGACY_JSON = true
return gson.toJsonTree(data)
} finally {
IS_WRITING_LEGACY_JSON = false
}
}
fun <T> writeLegacyJson(block: () -> T): T {
try {
IS_WRITING_LEGACY_JSON = true
return block.invoke()
} finally {
IS_WRITING_LEGACY_JSON = false
}
}
val gson: Gson = with(GsonBuilder()) {
serializeNulls()
// serializeNulls()
setDateFormat(DateFormat.LONG)
setFieldNamingPolicy(FieldNamingPolicy.IDENTITY)
setPrettyPrinting()
@ -215,18 +239,20 @@ object Starbound : ISBFileLocator {
registerTypeAdapter(ObjectDefinition::Adapter)
registerTypeAdapter(StatModifier::Adapter)
registerTypeAdapterFactory(NativeLegacy.Companion)
// математические классы
registerTypeAdapter(AABBTypeAdapter)
registerTypeAdapter(AABBiTypeAdapter)
registerTypeAdapter(Vector2dTypeAdapter)
registerTypeAdapter(Vector2fTypeAdapter)
registerTypeAdapter(Vector2iTypeAdapter)
registerTypeAdapter(Vector3dTypeAdapter)
registerTypeAdapter(Vector3fTypeAdapter)
registerTypeAdapter(Vector3iTypeAdapter)
registerTypeAdapter(Vector4iTypeAdapter)
registerTypeAdapter(Vector4dTypeAdapter)
registerTypeAdapter(Vector4fTypeAdapter)
registerTypeAdapter(AABBTypeAdapter.nullSafe())
registerTypeAdapter(AABBiTypeAdapter.nullSafe())
registerTypeAdapter(Vector2dTypeAdapter.nullSafe())
registerTypeAdapter(Vector2fTypeAdapter.nullSafe())
registerTypeAdapter(Vector2iTypeAdapter.nullSafe())
registerTypeAdapter(Vector3dTypeAdapter.nullSafe())
registerTypeAdapter(Vector3fTypeAdapter.nullSafe())
registerTypeAdapter(Vector3iTypeAdapter.nullSafe())
registerTypeAdapter(Vector4iTypeAdapter.nullSafe())
registerTypeAdapter(Vector4dTypeAdapter.nullSafe())
registerTypeAdapter(Vector4fTypeAdapter.nullSafe())
registerTypeAdapterFactory(Line2d.Companion)
registerTypeAdapterFactory(UniversePos.Companion)
registerTypeAdapterFactory(AbstractPerlinNoise.Companion)
@ -269,7 +295,6 @@ object Starbound : ISBFileLocator {
registerTypeAdapter(CelestialParameters::Adapter)
registerTypeAdapter(Particle::Adapter)
registerTypeAdapterFactory(BiomePlacementDistributionType.DATA_ADAPTER)
registerTypeAdapterFactory(BiomePlacementDistributionType.DEFINITION_ADAPTER)
registerTypeAdapterFactory(BiomePlacementItemType.DATA_ADAPTER)
registerTypeAdapterFactory(BiomePlacementItemType.DEFINITION_ADAPTER)
@ -278,6 +303,7 @@ object Starbound : ISBFileLocator {
// register companion first, so it has lesser priority than dispatching adapter
registerTypeAdapterFactory(VisitableWorldParametersType.Companion)
registerTypeAdapterFactory(VisitableWorldParametersType.ADAPTER)
registerTypeAdapter(WorldLayout.Companion)
Registries.registerAdapters(this)

View File

@ -10,6 +10,7 @@ import com.google.gson.stream.JsonReader
import com.google.gson.stream.JsonToken
import com.google.gson.stream.JsonWriter
import ru.dbotthepony.kommons.gson.consumeNull
import ru.dbotthepony.kommons.guava.immutableMap
import ru.dbotthepony.kstarbound.json.builder.JsonImplementation
@JsonImplementation(ThingDescription::class)
@ -84,6 +85,13 @@ data class ThingDescription(
val EMPTY = ThingDescription()
}
fun toMap(): ImmutableMap<String, String> {
return immutableMap {
putAll(racialDescription)
put("description", description)
}
}
fun fixDescription(newDescription: String): ThingDescription {
return copy(
shortdescription = if (shortdescription == "...") newDescription else shortdescription,

View File

@ -23,6 +23,7 @@ data class PerlinNoiseParameters(
}
enum class Type(override val jsonName: String) : IStringSerializable {
UNITIALIZED("uninitialized"),
PERLIN("perlin"),
BILLOW("billow"),
RIDGED_MULTI("ridgedMulti");

View File

@ -1,20 +1,18 @@
package ru.dbotthepony.kstarbound.defs.world
import com.google.common.collect.ImmutableList
import ru.dbotthepony.kstarbound.Registry
import ru.dbotthepony.kstarbound.defs.tile.MaterialModifier
import ru.dbotthepony.kstarbound.defs.tile.TileDefinition
import ru.dbotthepony.kstarbound.defs.world.AmbientNoisesDefinition
import ru.dbotthepony.kstarbound.defs.world.BiomePlaceables
import ru.dbotthepony.kstarbound.defs.world.Parallax
import ru.dbotthepony.kstarbound.json.NativeLegacy
import ru.dbotthepony.kstarbound.json.builder.JsonFactory
@JsonFactory
data class Biome(
val hueShift: Double = 0.0,
val materialHueShift: Int = 0,
val baseName: String,
val description: String,
val mainBlock: Registry.Entry<TileDefinition>? = null,
val subBlocks: ImmutableList<Registry.Entry<TileDefinition>> = ImmutableList.of(),
val ores: ImmutableList<Pair<Registry.Entry<MaterialModifier>, Double>> = ImmutableList.of(),
val mainBlock: NativeLegacy.Tile,
val subBlocks: ImmutableList<NativeLegacy.Tile> = ImmutableList.of(),
val ores: ImmutableList<Pair<NativeLegacy.TileMod, Double>> = ImmutableList.of(),
val musicTrack: AmbientNoisesDefinition? = null,
val ambientNoises: AmbientNoisesDefinition? = null,
val surfacePlaceables: BiomePlaceables = BiomePlaceables(),

View File

@ -11,9 +11,11 @@ import ru.dbotthepony.kstarbound.defs.AssetReference
import ru.dbotthepony.kstarbound.defs.JsonConfigFunction
import ru.dbotthepony.kstarbound.defs.tile.MaterialModifier
import ru.dbotthepony.kstarbound.defs.tile.TileDefinition
import ru.dbotthepony.kstarbound.json.NativeLegacy
import ru.dbotthepony.kstarbound.json.builder.JsonFactory
import ru.dbotthepony.kstarbound.json.pairListAdapter
import ru.dbotthepony.kstarbound.util.random.random
import ru.dbotthepony.kstarbound.world.positiveModulo
import java.util.random.RandomGenerator
@JsonFactory
@ -63,9 +65,10 @@ data class BiomeDefinition(
baseName = name,
description = description,
hueShift = hueShift,
materialHueShift = ((positiveModulo(hueShift, 360.0) / 360.0) * 255.0).toInt(),
mainBlock = mainBlock?.entry,
subBlocks = subBlocks?.stream()?.map { it.entry }?.filterNotNull()?.collect(ImmutableList.toImmutableList()) ?: ImmutableList.of(),
mainBlock = NativeLegacy.Tile(mainBlock?.entry),
subBlocks = subBlocks?.stream()?.map { NativeLegacy.Tile(it.entry) }?.filterNotNull()?.collect(ImmutableList.toImmutableList()) ?: ImmutableList.of(),
musicTrack = musicTrack,
ambientNoises = ambientNoises,
@ -77,10 +80,10 @@ data class BiomeDefinition(
ores = (ores?.value?.evaluate(threatLevel, oresAdapter)?.map {
it.stream()
.filter { it.second > 0.0 }
.map { Registries.tileModifiers[it.first] to it.second }
.filter { it.first != null }
.map { NativeLegacy.TileMod(Registries.tileModifiers.ref(it.first)) to it.second }
.filter { it.first.native.isPresent }
.collect(ImmutableList.toImmutableList())
}?.orElse(ImmutableList.of()) ?: ImmutableList.of()) as ImmutableList<Pair<Registry.Entry<MaterialModifier>, Double>>
}?.orElse(ImmutableList.of()) ?: ImmutableList.of())
)
}

View File

@ -5,6 +5,7 @@ import com.google.common.collect.ImmutableSet
import com.google.gson.Gson
import com.google.gson.JsonArray
import com.google.gson.JsonElement
import com.google.gson.JsonObject
import com.google.gson.JsonPrimitive
import com.google.gson.JsonSyntaxException
import com.google.gson.TypeAdapter
@ -19,7 +20,9 @@ import ru.dbotthepony.kommons.gson.value
import ru.dbotthepony.kstarbound.Registry
import ru.dbotthepony.kstarbound.Starbound
import ru.dbotthepony.kstarbound.collect.WeightedList
import ru.dbotthepony.kstarbound.defs.PerlinNoiseParameters
import ru.dbotthepony.kstarbound.defs.tile.MaterialModifier
import ru.dbotthepony.kstarbound.json.NativeLegacy
import ru.dbotthepony.kstarbound.json.builder.JsonFactory
import ru.dbotthepony.kstarbound.json.builder.JsonFlat
import ru.dbotthepony.kstarbound.json.listAdapter
@ -28,14 +31,14 @@ import java.util.stream.Stream
@JsonFactory
data class BiomePlaceables(
val grassMod: Registry.Entry<MaterialModifier>? = null,
val ceilingGrassMod: Registry.Entry<MaterialModifier>? = null,
val grassMod: NativeLegacy.TileMod = NativeLegacy.TileMod(null as Registry.Ref<MaterialModifier>?),
val ceilingGrassMod: NativeLegacy.TileMod = NativeLegacy.TileMod(null as Registry.Ref<MaterialModifier>?),
val grassModDensity: Double = 0.0,
val ceilingGrassModDensity: Double = 0.0,
val items: ImmutableList<DistributionItem> = ImmutableList.of(),
val itemDistributions: ImmutableList<DistributionItem> = ImmutableList.of(),
) {
fun firstTreeVariant(): TreeVariant? {
return items.stream()
return itemDistributions.stream()
.flatMap { it.data.itemStream() }
.map { it as? Tree }
.filterNotNull()
@ -107,7 +110,7 @@ data class BiomePlaceables(
))
"grass" -> Grass(grassVariant.read(`in`))
"bush" -> Bush(bushVariant.read(`in`))
"tree" -> Tree(trees.read(`in`))
"treePair" -> Tree(trees.read(`in`))
"objectPool" -> Object(objects.read(`in`))
else -> throw JsonSyntaxException("Unknown biome placement item $type")
}
@ -187,39 +190,28 @@ data class BiomePlaceables(
}
}
// -------- DISTRIBUTION
abstract class DistributionData {
abstract val type: BiomePlacementDistributionType
abstract fun itemStream(): Stream<Item>
}
// ТЕПЕРЬ УГАДАЙ
// МОЙ
// ПОЛ.
// БЛЯДЬ
@JsonFactory
data class RandomDistribution(
val blockSeed: Long,
val randomItems: ImmutableList<Item>,
) : DistributionData() {
override val type: BiomePlacementDistributionType
get() = BiomePlacementDistributionType.RANDOM
override fun itemStream(): Stream<Item> {
return randomItems.stream()
}
}
@JsonFactory
data class PeriodicDistribution(
val modulus: Int,
val modulusOffset: Int,
val densityFunction: AbstractPerlinNoise,
val modulusDistortion: AbstractPerlinNoise,
val weightedItems: ImmutableList<Pair<Item, AbstractPerlinNoise>>,
) : DistributionData() {
override val type: BiomePlacementDistributionType
get() = BiomePlacementDistributionType.PERIODIC
override fun itemStream(): Stream<Item> {
return weightedItems.stream().map { it.first }
data class DistributionData(
val distribution: BiomePlacementDistributionType,
val blockSeed: Long = 0L,
val blockProbability: Double = 0.0,
val randomItems: ImmutableList<Item> = ImmutableList.of(),
val modulus: Int = 0,
val modulusOffset: Int = 0,
val densityFunction: AbstractPerlinNoise = AbstractPerlinNoise.of(PerlinNoiseParameters()),
val modulusDistortion: AbstractPerlinNoise = AbstractPerlinNoise.of(PerlinNoiseParameters()),
val weightedItems: ImmutableList<Pair<Item, AbstractPerlinNoise>> = ImmutableList.of(),
) {
fun itemStream(): Stream<Item> {
if (distribution == BiomePlacementDistributionType.RANDOM) {
return randomItems.stream()
} else {
return weightedItems.stream().map { it.first }
}
}
}
}

View File

@ -11,6 +11,8 @@ import ru.dbotthepony.kstarbound.collect.WeightedList
import ru.dbotthepony.kstarbound.defs.AssetReference
import ru.dbotthepony.kstarbound.defs.PerlinNoiseParameters
import ru.dbotthepony.kstarbound.defs.tile.MaterialModifier
import ru.dbotthepony.kstarbound.json.NativeLegacy
import ru.dbotthepony.kstarbound.json.builder.IStringSerializable
import ru.dbotthepony.kstarbound.json.builder.JsonFactory
import ru.dbotthepony.kstarbound.json.builder.JsonFlat
import ru.dbotthepony.kstarbound.json.builder.JsonSingleton
@ -27,8 +29,8 @@ data class BiomePlaceablesDefinition(
val ceilingGrassModDensity: Double = 0.0,
val items: ImmutableList<DistributionItem> = ImmutableList.of(),
) {
enum class Placement {
FLOOR, CEILING, BACKGROUND, OCEAN;
enum class Placement(override val jsonName: String) : IStringSerializable {
FLOOR("floor"), CEILING("ceiling"), BACKGROUND("background"), OCEAN("ocean");
}
// ----------- ITEMS
@ -251,8 +253,8 @@ data class BiomePlaceablesDefinition(
abstract fun create(self: DistributionItem, biome: BiomeDefinition.CreationParams): BiomePlaceables.DistributionData
}
@JsonSingleton
object RandomDistribution : DistributionData() {
@JsonFactory
data class RandomDistribution(val blockProbability: Double) : DistributionData() {
override val type: BiomePlacementDistributionType
get() = BiomePlacementDistributionType.RANDOM
@ -260,8 +262,10 @@ data class BiomePlaceablesDefinition(
self: DistributionItem,
biome: BiomeDefinition.CreationParams
): BiomePlaceables.DistributionData {
return BiomePlaceables.RandomDistribution(
return BiomePlaceables.DistributionData(
distribution = BiomePlacementDistributionType.RANDOM,
blockSeed = biome.random.nextLong(),
blockProbability = blockProbability,
randomItems = IntStream.range(0, self.variants)
.mapToObj { self.data.create(biome) }
.collect(ImmutableList.toImmutableList())
@ -291,7 +295,8 @@ data class BiomePlaceablesDefinition(
): BiomePlaceables.DistributionData {
val modulusOffset = if (modulus == 0) 0 else biome.random.nextInt(-modulus, modulus)
return BiomePlaceables.PeriodicDistribution(
return BiomePlaceables.DistributionData(
distribution = BiomePlacementDistributionType.PERIODIC,
modulus = modulus,
modulusOffset = modulusOffset,
@ -350,11 +355,11 @@ data class BiomePlaceablesDefinition(
fun create(biome: BiomeDefinition.CreationParams): BiomePlaceables {
return BiomePlaceables(
grassMod = grassMod.random(biome.random) { null }?.entry,
ceilingGrassMod = ceilingGrassMod.random(biome.random) { null }?.entry,
grassMod = NativeLegacy.TileMod(grassMod.random(biome.random) { null }?.entry),
ceilingGrassMod = NativeLegacy.TileMod(ceilingGrassMod.random(biome.random) { null }?.entry),
grassModDensity = grassModDensity,
ceilingGrassModDensity = ceilingGrassModDensity,
items = items.stream()
itemDistributions = items.stream()
.map { it.create(biome) }
.collect(ImmutableList.toImmutableList())
)

View File

@ -7,16 +7,9 @@ import ru.dbotthepony.kstarbound.json.builder.IStringSerializable
enum class BiomePlacementDistributionType(
override val jsonName: String,
val def: TypeToken<out BiomePlaceablesDefinition.DistributionData>,
val data: TypeToken<out BiomePlaceables.DistributionData>,
) : IStringSerializable {
RANDOM("random",
TypeToken.get(BiomePlaceablesDefinition.RandomDistribution::class.java),
TypeToken.get(BiomePlaceables.RandomDistribution::class.java)
),
PERIODIC("periodic",
TypeToken.get(BiomePlaceablesDefinition.PeriodicDistribution::class.java),
TypeToken.get(BiomePlaceables.PeriodicDistribution::class.java)
);
RANDOM("random",TypeToken.get(BiomePlaceablesDefinition.RandomDistribution::class.java)),
PERIODIC("periodic", TypeToken.get(BiomePlaceablesDefinition.PeriodicDistribution::class.java));
override fun match(name: String): Boolean {
return name.lowercase() == jsonName
@ -24,6 +17,5 @@ enum class BiomePlacementDistributionType(
companion object {
val DEFINITION_ADAPTER = DispatchingAdapter("type", { type }, { def }, entries)
val DATA_ADAPTER = DispatchingAdapter("type", { type }, { data }, entries)
}
}

View File

@ -26,14 +26,13 @@ class BushVariant(
val baseHueShift: Double,
val modHueShift: Double,
@JsonFlat
val descriptions: ThingDescription,
val descriptions: ImmutableMap<String, String> = ImmutableMap.of(),
val ceiling: Boolean,
val ephemeral: Boolean,
val tileDamageParameters: TileDamageConfig,
) {
@JsonFactory
@JsonFactory(asList = true)
data class Shape(val image: String, val mods: ImmutableList<String>)
@JsonFactory
@ -64,7 +63,7 @@ class BushVariant(
directory = data.file?.computeDirectory() ?: "/",
modHueShift = modHueShift,
ceiling = data.value.ceiling,
descriptions = data.value.descriptions.fixDescription("${data.key} with $modName"),
descriptions = data.value.descriptions.fixDescription("${data.key} with $modName").toMap(),
ephemeral = data.value.ephemeral,
tileDamageParameters = (data.value.damageTable?.value ?: GlobalDefaults.bushDamage).copy(totalHealth = data.value.health),
modName = modName,

View File

@ -11,7 +11,9 @@ 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.gson.set
import ru.dbotthepony.kommons.gson.value
import ru.dbotthepony.kstarbound.Starbound
import ru.dbotthepony.kstarbound.json.builder.JsonFactory
import ru.dbotthepony.kstarbound.util.random.random
import ru.dbotthepony.kstarbound.world.UniversePos

View File

@ -1,5 +1,6 @@
package ru.dbotthepony.kstarbound.defs.world
import com.google.common.collect.ImmutableMap
import com.google.common.collect.ImmutableSet
import com.google.gson.JsonElement
import ru.dbotthepony.kstarbound.GlobalDefaults
@ -17,8 +18,7 @@ data class GrassVariant(
val directory: String,
val images: ImmutableSet<String> = ImmutableSet.of(),
val hueShift: Double,
@JsonFlat
val descriptions: ThingDescription,
val descriptions: ImmutableMap<String, String> = ImmutableMap.of(),
val ceiling: Boolean,
val ephemeral: Boolean,
val tileDamageParameters: TileDamageConfig,
@ -53,7 +53,7 @@ data class GrassVariant(
ceiling = data.value.ceiling,
ephemeral = data.value.ephemeral,
hueShift = hueShift,
descriptions = data.value.descriptions.fixDescription(data.value.name),
descriptions = data.value.descriptions.fixDescription(data.value.name).toMap(),
tileDamageParameters = (data.value.damageTable?.value ?: GlobalDefaults.grassDamage).copy(totalHealth = data.value.health)
)
}

View File

@ -2,6 +2,7 @@ package ru.dbotthepony.kstarbound.defs.world
import com.google.common.collect.ImmutableList
import ru.dbotthepony.kommons.collect.filterNotNull
import ru.dbotthepony.kommons.gson.get
import ru.dbotthepony.kommons.math.RGBAColor
import ru.dbotthepony.kommons.util.Either
import ru.dbotthepony.kommons.vector.Vector2d
@ -80,7 +81,7 @@ class Parallax(
if (isFoliage) {
if (treeVariant == null) return null
if (!treeVariant.foliageSettings.parallaxFoliage) return null
if (!treeVariant.foliageSettings.asJsonObject.get("parallaxFoliage", false)) return null
texPath = "${treeVariant.foliageDirectory}/parallax/${kind.replace("foliage/", "")}/"
} else if (isStem) {
if (treeVariant == null) return null

View File

@ -1,10 +1,8 @@
package ru.dbotthepony.kstarbound.defs.world
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.JsonNull
import com.google.gson.JsonObject
import com.google.gson.JsonPrimitive
import com.google.gson.TypeAdapter
@ -24,13 +22,11 @@ import ru.dbotthepony.kstarbound.Starbound
import ru.dbotthepony.kstarbound.collect.WeightedList
import ru.dbotthepony.kstarbound.defs.JsonDriven
import ru.dbotthepony.kstarbound.defs.PerlinNoiseParameters
import ru.dbotthepony.kstarbound.fromJson
import ru.dbotthepony.kstarbound.json.builder.JsonFactory
import ru.dbotthepony.kstarbound.json.pairAdapter
import ru.dbotthepony.kstarbound.json.stream
import ru.dbotthepony.kstarbound.util.binnedChoice
import ru.dbotthepony.kstarbound.util.random.AbstractPerlinNoise
import ru.dbotthepony.kstarbound.util.random.PerlinNoise
import ru.dbotthepony.kstarbound.util.random.nextRange
import ru.dbotthepony.kstarbound.util.random.random
import java.util.random.RandomGenerator
@ -100,13 +96,13 @@ class TerrestrialWorldParameters : VisitableWorldParameters() {
val read = Starbound.gson.fromJson(data, StoreData::class.java)
primaryBiome = read.primaryBiome
primarySurfaceLiquid = read.primarySurfaceLiquid
surfaceLiquid = read.surfaceLiquid
sizeName = read.sizeName
hueShift = read.hueShift
skyColoring = read.skyColoring
dayLength = read.dayLength
blockNoiseConfig = read.blockNoiseConfig
blendNoiseConfig = read.blendNoiseConfig
blockNoiseConfig = read.blockNoise
blendNoiseConfig = read.blendNoise
blendSize = read.blendSize
spaceLayer = read.spaceLayer
atmosphereLayer = read.atmosphereLayer
@ -124,23 +120,23 @@ class TerrestrialWorldParameters : VisitableWorldParameters() {
// original engine operate on liquids solely with IDs
// and we also need to network this json to legacy clients.
// what a shame :JC:.
if (this.primarySurfaceLiquid == null) {
if (this.surfaceLiquid == null) {
primarySurfaceLiquid = if (isLegacy) Either.left(0) else null
} else if (isLegacy) {
primarySurfaceLiquid = this.primarySurfaceLiquid!!.map({ it }, { Registries.liquid.get(it)!!.id })?.let { Either.left(it) }
primarySurfaceLiquid = this.surfaceLiquid!!.map({ it }, { Registries.liquid.get(it)!!.id })?.let { Either.left(it) }
} else {
primarySurfaceLiquid = this.primarySurfaceLiquid
primarySurfaceLiquid = this.surfaceLiquid
}
val store = StoreData(
primaryBiome = primaryBiome,
primarySurfaceLiquid = primarySurfaceLiquid,
surfaceLiquid = primarySurfaceLiquid,
sizeName = sizeName,
hueShift = hueShift,
skyColoring = skyColoring,
dayLength = dayLength,
blockNoiseConfig = blockNoiseConfig,
blendNoiseConfig = blendNoiseConfig,
blockNoise = blockNoiseConfig,
blendNoise = blendNoiseConfig,
blendSize = blendSize,
spaceLayer = spaceLayer,
atmosphereLayer = atmosphereLayer,
@ -158,13 +154,13 @@ class TerrestrialWorldParameters : VisitableWorldParameters() {
@JsonFactory
data class StoreData(
val primaryBiome: String,
val primarySurfaceLiquid: Either<Int, String>? = null,
val surfaceLiquid: Either<Int, String>? = null,
val sizeName: String,
val hueShift: Double,
val skyColoring: SkyColoring,
val dayLength: Double,
val blockNoiseConfig: BlockNoiseConfig? = null,
val blendNoiseConfig: PerlinNoiseParameters? = null,
val blockNoise: BlockNoiseConfig? = null,
val blendNoise: PerlinNoiseParameters? = null,
val blendSize: Double,
val spaceLayer: Layer,
val atmosphereLayer: Layer,
@ -176,7 +172,7 @@ class TerrestrialWorldParameters : VisitableWorldParameters() {
var primaryBiome: String by Delegates.notNull()
private set
var primarySurfaceLiquid: Either<Int, String>? = null
var surfaceLiquid: Either<Int, String>? = null
private set
var sizeName: String by Delegates.notNull()
private set
@ -454,7 +450,7 @@ class TerrestrialWorldParameters : VisitableWorldParameters() {
parameters.sizeName = sizeName
parameters.hueShift = primaryBiome.value.hueShift(random)
parameters.primarySurfaceLiquid = surfaceLayer.primaryRegion.oceanLiquid ?: surfaceLayer.primaryRegion.caveLiquid
parameters.surfaceLiquid = surfaceLayer.primaryRegion.oceanLiquid ?: surfaceLayer.primaryRegion.caveLiquid
parameters.skyColoring = primaryBiome.value.skyColoring(random)
parameters.dayLength = random.nextRange(params.dayLengthRange)

View File

@ -1,6 +1,8 @@
package ru.dbotthepony.kstarbound.defs.world
import com.google.common.collect.ImmutableMap
import com.google.gson.JsonElement
import com.google.gson.JsonNull
import com.google.gson.JsonObject
import ru.dbotthepony.kstarbound.GlobalDefaults
import ru.dbotthepony.kstarbound.Registries
@ -24,11 +26,10 @@ data class TreeVariant(
val foliageDirectory: String,
// AGAIN.
val foliageSettings: FoliageData,
val foliageSettings: JsonElement,
val foliageHueShift: Double,
@JsonFlat
val descriptions: ThingDescription,
val descriptions: ImmutableMap<String, String> = ImmutableMap.of(),
val ceiling: Boolean,
val ephemeral: Boolean,
@ -78,11 +79,11 @@ data class TreeVariant(
stemHueShift = stemHueShift,
ceiling = data.value.ceiling,
stemDropConfig = data.value.dropConfig.deepCopy(),
descriptions = data.value.descriptions.fixDescription(data.key),
descriptions = data.value.descriptions.fixDescription(data.key).toMap(),
ephemeral = data.value.ephemeral,
tileDamageParameters = (data.value.damageTable?.value ?: GlobalDefaults.treeDamage).copy(totalHealth = data.value.health),
foliageSettings = FoliageData("", shape = ""),
foliageSettings = JsonNull.INSTANCE,
foliageDropConfig = JsonObject(),
foliageName = "",
foliageDirectory = "/",
@ -105,11 +106,11 @@ data class TreeVariant(
stemHueShift = stemHueShift,
ceiling = data.value.ceiling,
stemDropConfig = data.value.dropConfig.deepCopy(),
descriptions = data.value.descriptions.fixDescription("${data.key} with ${fdata.key}"),
descriptions = data.value.descriptions.fixDescription("${data.key} with ${fdata.key}").toMap(),
ephemeral = data.value.ephemeral,
tileDamageParameters = (data.value.damageTable?.value ?: GlobalDefaults.treeDamage).copy(totalHealth = data.value.health),
foliageSettings = fdata.value,
foliageSettings = fdata.json,
foliageDropConfig = fdata.value.dropConfig.deepCopy(),
foliageName = fdata.key,
foliageDirectory = fdata.file?.computeDirectory() ?: "/",

View File

@ -51,7 +51,7 @@ enum class VisitableWorldParametersType(override val jsonName: String, val token
if (value == null)
out.nullValue()
else
out.value(value.toJson(false))
out.value(value.toJson())
}
override fun read(`in`: JsonReader): VisitableWorldParameters? {
@ -140,7 +140,7 @@ abstract class VisitableWorldParameters {
this.weatherPool = read.weatherPool
}
open fun toJson(data: JsonObject, isLegacy: Boolean) {
open fun toJson(data: JsonObject, isLegacy: Boolean = Starbound.IS_WRITING_LEGACY_JSON) {
val store = StoreData(
threatLevel,
typeName,
@ -164,7 +164,7 @@ abstract class VisitableWorldParameters {
data["type"] = type.jsonName
}
fun toJson(isLegacy: Boolean): JsonObject {
fun toJson(isLegacy: Boolean = Starbound.IS_WRITING_LEGACY_JSON): JsonObject {
val data = JsonObject()
toJson(data, isLegacy)
return data

View File

@ -4,12 +4,17 @@ import com.google.common.collect.ImmutableList
import com.google.gson.JsonArray
import com.google.gson.JsonObject
import com.google.gson.JsonPrimitive
import com.google.gson.TypeAdapter
import com.google.gson.reflect.TypeToken
import com.google.gson.stream.JsonReader
import com.google.gson.stream.JsonWriter
import it.unimi.dsi.fastutil.doubles.DoubleArrayList
import it.unimi.dsi.fastutil.ints.IntArrayList
import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet
import ru.dbotthepony.kommons.gson.JsonArrayCollector
import ru.dbotthepony.kommons.gson.consumeNull
import ru.dbotthepony.kommons.gson.set
import ru.dbotthepony.kommons.gson.value
import ru.dbotthepony.kommons.math.RGBAColor
import ru.dbotthepony.kommons.util.AABBi
import ru.dbotthepony.kommons.util.Either
@ -202,11 +207,11 @@ class WorldLayout {
val layers: JsonArray,
)
fun toJson(isLegacy: Boolean): JsonObject {
fun toJson(): JsonObject {
return Starbound.gson.toJsonTree(SerializedForm(
worldSize, regionBlending, blockNoise, blendNoise,
playerStartSearchRegions, biomes.list, terrainSelectors.list,
layers = layers.stream().map { it.toJson(isLegacy) }.collect(JsonArrayCollector)
layers = layers.stream().map { it.toJson(Starbound.IS_WRITING_LEGACY_JSON) }.collect(JsonArrayCollector)
)) as JsonObject
}
@ -410,4 +415,24 @@ class WorldLayout {
biome.parallax?.fadeToSkyColor(skyColoring)
}
}
companion object : TypeAdapter<WorldLayout>() {
private val objects by lazy { Starbound.gson.getAdapter(JsonObject::class.java) }
override fun write(out: JsonWriter, value: WorldLayout?) {
if (value == null)
out.nullValue()
else
out.value(value.toJson())
}
override fun read(`in`: JsonReader): WorldLayout? {
if (`in`.consumeNull())
return null
val params = WorldLayout()
params.fromJson(objects.read(`in`))
return params
}
}
}

View File

@ -4,6 +4,7 @@ import com.google.common.collect.ImmutableList
import com.google.common.collect.ImmutableMap
import com.google.gson.JsonElement
import com.google.gson.JsonNull
import com.google.gson.JsonObject
import ru.dbotthepony.kommons.util.AABBi
import ru.dbotthepony.kommons.util.Either
import ru.dbotthepony.kommons.vector.Vector2d
@ -15,7 +16,7 @@ import ru.dbotthepony.kstarbound.world.Direction1D
data class WorldStructure(
val region: AABBi = AABBi(Vector2i.ZERO, Vector2i.ZERO),
val anchorPosition: Vector2i = Vector2i.ZERO,
val config: JsonElement = JsonNull.INSTANCE,
var config: JsonElement = JsonObject(),
val backgroundOverlays: ImmutableList<Overlay> = ImmutableList.of(),
val foregroundOverlays: ImmutableList<Overlay> = ImmutableList.of(),
val backgroundBlocks: ImmutableList<Block> = ImmutableList.of(),
@ -23,6 +24,13 @@ data class WorldStructure(
val objects: ImmutableList<Obj> = ImmutableList.of(),
val flaggedBlocks: ImmutableMap<String, ImmutableList<Vector2i>> = ImmutableMap.of(),
) {
init {
if (config == JsonNull.INSTANCE) {
// so it is not omitted
config = JsonObject()
}
}
@JsonFactory
data class Overlay(val min: Vector2d, val image: String, val fullbright: Boolean)

View File

@ -51,17 +51,17 @@ class WorldTemplate(val geometry: WorldGeometry) {
val skyParameters: SkyParameters = SkyParameters(),
val seed: Long = 0L,
val size: Either<WorldGeometry, Vector2i>,
val regionData: JsonElement = JsonNull.INSTANCE,
val regionData: WorldLayout? = null,
//val customTerrainRegions:
)
fun toJson(isLegacy: Boolean): JsonObject {
fun toJson(): JsonObject {
val data = Starbound.gson.toJsonTree(SerializedForm(
celestialParameters, worldParameters, skyParameters, seed,
if (isLegacy) Either.right(geometry.size) else Either.left(geometry),
if (Starbound.IS_WRITING_LEGACY_JSON) Either.right(geometry.size) else Either.left(geometry),
worldLayout
)) as JsonObject
data["regionData"] = worldLayout?.toJson(isLegacy) ?: JsonNull.INSTANCE
return data
}
@ -91,7 +91,7 @@ class WorldTemplate(val geometry: WorldGeometry) {
template.worldParameters = load.worldParameters
template.skyParameters = load.skyParameters
template.seed = load.seed
template.worldLayout = load.regionData.let { if (it is JsonObject) WorldLayout().fromJson(it) else null }
template.worldLayout = load.regionData
template.determineName()

View File

@ -0,0 +1,146 @@
package ru.dbotthepony.kstarbound.json
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.Either
import ru.dbotthepony.kommons.util.KOptional
import ru.dbotthepony.kstarbound.Registries
import ru.dbotthepony.kstarbound.Registry
import ru.dbotthepony.kstarbound.Starbound
import ru.dbotthepony.kstarbound.defs.tile.BuiltinMetaMaterials
import ru.dbotthepony.kstarbound.defs.tile.MaterialModifier
import ru.dbotthepony.kstarbound.defs.tile.TileDefinition
import java.lang.reflect.Constructor
import java.lang.reflect.ParameterizedType
/**
* Creates single point of reference for both native json and legacy json
* values
*/
abstract class NativeLegacy<NATIVE, LEGACY> {
protected abstract fun computeLegacy(value: NATIVE): LEGACY
protected abstract fun computeNative(value: LEGACY): NATIVE
protected var nativeValue: KOptional<NATIVE> = KOptional()
protected var legacyValue: KOptional<LEGACY> = KOptional()
val legacy: LEGACY get() {
if (legacyValue.isEmpty) {
val compute = computeLegacy(nativeValue.value)
legacyValue = KOptional(compute)
return compute
}
return legacyValue.value
}
val native: NATIVE get() {
if (nativeValue.isEmpty) {
val compute = computeNative(legacyValue.value)
nativeValue = KOptional(compute)
return compute
}
return nativeValue.value
}
class Tile() : NativeLegacy<Registry.Ref<TileDefinition>, Int?>() {
constructor(tile: Registry.Entry<TileDefinition>?) : this() {
if (tile == null)
legacyValue = KOptional(65535)
else
nativeValue = KOptional(tile.ref)
}
constructor(tile: Registry.Ref<TileDefinition>?) : this() {
if (tile == null)
legacyValue = KOptional(65535)
else
nativeValue = KOptional(tile)
}
override fun computeLegacy(value: Registry.Ref<TileDefinition>): Int {
return value.value?.materialId ?: 65535
}
override fun computeNative(value: Int?): Registry.Ref<TileDefinition> {
value ?: return Registries.tiles.emptyRef
return Registries.tiles.ref(value)
}
}
class TileMod() : NativeLegacy<Registry.Ref<MaterialModifier>, Int?>() {
constructor(tile: Registry.Entry<MaterialModifier>?) : this() {
if (tile == null)
legacyValue = KOptional(65535)
else
nativeValue = KOptional(tile.ref)
}
constructor(tile: Registry.Ref<MaterialModifier>?) : this() {
if (tile == null)
legacyValue = KOptional(65535)
else
nativeValue = KOptional(tile)
}
override fun computeLegacy(value: Registry.Ref<MaterialModifier>): Int {
return value.value?.modId ?: 65535
}
override fun computeNative(value: Int?): Registry.Ref<MaterialModifier> {
value ?: return Registries.tileModifiers.emptyRef
return Registries.tileModifiers.ref(value)
}
}
private class Adapter<L, R>(gson: Gson, left: TypeToken<L>, right: TypeToken<R>, private val constructor: Constructor<NativeLegacy<L, R>>) : TypeAdapter<NativeLegacy<L, R>>() {
private val left = gson.getAdapter(left)
private val right = gson.getAdapter(right)
private val either = gson.getAdapter(TypeToken.getParameterized(Either::class.java, left.type, right.type)) as TypeAdapter<Either<L, R>>
override fun write(out: JsonWriter, value: NativeLegacy<L, R>?) {
if (value == null)
out.nullValue()
else if (Starbound.IS_WRITING_LEGACY_JSON || value.nativeValue.isEmpty)
right.write(out, value.legacy)
else
left.write(out, value.native)
}
override fun read(`in`: JsonReader): NativeLegacy<L, R>? {
if (`in`.consumeNull())
return null
val instance = constructor.newInstance()
val either = either.read(`in`)
instance.nativeValue = either.left
instance.legacyValue = either.right
return instance
}
}
companion object : TypeAdapterFactory {
@Suppress("UNCHECKED_CAST")
override fun <T : Any?> create(gson: Gson, type: TypeToken<T>): TypeAdapter<T>? {
if (NativeLegacy::class.java.isAssignableFrom(type.rawType)) {
val rawType = type.rawType as? Class<*> ?: throw RuntimeException("bro what the fuck?")
val superclass = rawType.genericSuperclass as? ParameterizedType ?: throw RuntimeException("bro what the fuck?")
return Adapter(
gson,
TypeToken.get(superclass.actualTypeArguments[0]) as TypeToken<Any>,
TypeToken.get(superclass.actualTypeArguments[1]) as TypeToken<Any>,
rawType.getDeclaredConstructor() as Constructor<NativeLegacy<Any, Any>>
) as TypeAdapter<T>
}
return null
}
}
}

View File

@ -148,10 +148,15 @@ class FactoryAdapter<T : Any> private constructor(
return
}
out.beginObject()
if (asJsonArray)
out.beginArray()
else
out.beginObject()
for (type in types) {
if (type.isFlat) {
check(!asJsonArray)
val (field, adapter) = type
val result = (adapter as TypeAdapter<Any>).toJsonTree((field as KProperty1<T, Any>).get(value))
@ -160,17 +165,26 @@ class FactoryAdapter<T : Any> private constructor(
throw JsonSyntaxException("Expected JsonObject from adapter of ${type.name}, but got ${result::class.qualifiedName}")
}
out.value(result)
for ((k, v) in result.entrySet()) {
out.name(k)
out.value(v)
}
}
} else {
val (field, adapter) = type
out.name(field.name)
if (!asJsonArray)
out.name(field.name)
@Suppress("unchecked_cast")
(adapter as TypeAdapter<Any>).write(out, (field as KProperty1<T, Any>).get(value))
}
}
out.endObject()
if (asJsonArray)
out.endArray()
else
out.endObject()
}
override fun read(reader: JsonReader): T? {

View File

@ -121,6 +121,10 @@ class FloatingNetworkedElement(private var value: Double = 0.0, val ops: Ops, va
}
queue.clear()
if (isInterpolating) {
queue.add(currentTime to value)
}
}
override fun writeInitial(data: DataOutputStream, isLegacy: Boolean) {

View File

@ -322,18 +322,23 @@ class ServerConnection(val server: StarboundServer, type: ConnectionType) : Conn
shipWorld = it
shipWorld.thread.start()
send(PlayerWarpResultPacket(true, WarpAlias.OwnShip, false))
shipWorld.acceptPlayer(this).thenAccept {
server.worlds.first().acceptPlayer(this)
/*shipWorld.acceptPlayer(this).thenAccept {
for (conn in server.channels.connections) {
if (conn.isLegacy && conn !== this) {
conn.shipWorld.acceptPlayer(this)
break
}
}
server.worlds.first().acceptPlayer(this)
}.exceptionally {
LOGGER.error("Shipworld of $this rejected to accept its owner", it)
disconnect("Shipworld rejected player warp request: $it")
null
}
}*/
}.exceptionally {
LOGGER.error("Error while initializing shipworld for $this", it)
disconnect("Error while initializing shipworld for player: $it")

View File

@ -72,7 +72,7 @@ class ServerWorld private constructor(
player.skyVersion = skyVersion
player.send(WorldStartPacket(
templateData = template.toJson(true),
templateData = Starbound.writeLegacyJson { template.toJson() },
skyData = skyData.toByteArray(),
weatherData = ByteArray(0),
playerStart = playerSpawnPosition,
@ -86,7 +86,9 @@ class ServerWorld private constructor(
localInterpolationMode = false,
))
player.sendAndFlush(CentralStructureUpdatePacket(Starbound.gson.toJsonTree(centralStructure)))
Starbound.writeLegacyJson {
player.sendAndFlush(CentralStructureUpdatePacket(Starbound.gson.toJsonTree(centralStructure)))
}
} else {
player.sendAndFlush(JoinWorldPacket(this))
}
@ -97,7 +99,9 @@ class ServerWorld private constructor(
unpause()
try {
return CompletableFuture.supplyAsync(Supplier { doAcceptPlayer(player) }, mailbox)
return CompletableFuture.supplyAsync(Supplier { doAcceptPlayer(player) }, mailbox).exceptionally {
LOGGER.error("Error while accepting new player into world", it)
}
} catch (err: RejectedExecutionException) {
return CompletableFuture.failedFuture(err)
}
@ -123,7 +127,10 @@ class ServerWorld private constructor(
check(!isClosed.get()) { "$this is invalid" }
try {
return CompletableFuture.supplyAsync(Supplier { doRemovePlayer(player) }, mailbox)
return CompletableFuture.supplyAsync(Supplier { doRemovePlayer(player) }, mailbox).exceptionally {
LOGGER.error("Error while removing player from world", it)
null
}
} catch (err: RejectedExecutionException) {
return CompletableFuture.completedFuture(false)
}

View File

@ -20,7 +20,7 @@ object AssetPathStack {
push(value.substringBefore(':').substringBeforeLast('/'))
}
fun last() = stack.lastOrNull()
fun last() = stack.lastOrNull() ?: "/"
fun pop() = stack.removeLast()
inline fun <T> block(path: String, block: (String) -> T): T {
@ -44,11 +44,11 @@ object AssetPathStack {
}
fun remap(path: String): String {
return remap(checkNotNull(last()) { "Not reading an asset on current thread" }, path)
return remap(last(), path)
}
fun remapSafe(path: String): String {
return remap(last() ?: return path, path)
return remap(last(), path)
}
fun relativeTo(base: String, path: String): String {

View File

@ -43,12 +43,15 @@ abstract class AbstractPerlinNoise(val parameters: PerlinNoiseParameters) {
protected val g3 by lazy { Double2DArray.allocate(parameters.scale * 2 + 2, 3) }
init {
if (parameters.seed != null) {
if (parameters.seed != null && parameters.type != PerlinNoiseParameters.Type.UNITIALIZED) {
init(parameters.seed)
}
}
fun init(seed: Long) {
if (parameters.type == PerlinNoiseParameters.Type.UNITIALIZED)
return
isInitialized = true
this.seed = seed
@ -218,6 +221,7 @@ abstract class AbstractPerlinNoise(val parameters: PerlinNoiseParameters) {
PerlinNoiseParameters.Type.PERLIN -> PerlinNoise(parameters)
PerlinNoiseParameters.Type.BILLOW -> BillowNoise(parameters)
PerlinNoiseParameters.Type.RIDGED_MULTI -> RidgedNoise(parameters)
PerlinNoiseParameters.Type.UNITIALIZED -> EmptyNoise(parameters)
}
}

View File

@ -0,0 +1,21 @@
package ru.dbotthepony.kstarbound.util.random
import ru.dbotthepony.kstarbound.defs.PerlinNoiseParameters
class EmptyNoise(parameters: PerlinNoiseParameters) : AbstractPerlinNoise(parameters) {
init {
require(parameters.type == PerlinNoiseParameters.Type.UNITIALIZED)
}
override fun get(x: Double): Double {
return 0.0
}
override fun get(x: Double, y: Double): Double {
return 0.0
}
override fun get(x: Double, y: Double, z: Double): Double {
return 0.0
}
}