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 { Starbound.mailboxInitialized.submit {
val server = IntegratedStarboundServer(File("./")) val server = IntegratedStarboundServer(File("./"))
val world = ServerWorld.load(server, LegacyWorldStorage.file(db)).get() val world = ServerWorld.load(server, LegacyWorldStorage.file(db)).get()
//world.thread.start() world.thread.start()
//ply = PlayerEntity(client.world!!) //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.BiomePlaceables
import ru.dbotthepony.kstarbound.defs.world.BiomePlacementDistributionType import ru.dbotthepony.kstarbound.defs.world.BiomePlacementDistributionType
import ru.dbotthepony.kstarbound.defs.world.BiomePlacementItemType 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.world.terrain.TerrainSelectorType
import ru.dbotthepony.kstarbound.io.* import ru.dbotthepony.kstarbound.io.*
import ru.dbotthepony.kstarbound.json.factory.MapsTypeAdapterFactory 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.math.*
import ru.dbotthepony.kstarbound.server.world.UniverseChunk import ru.dbotthepony.kstarbound.server.world.UniverseChunk
import ru.dbotthepony.kstarbound.item.ItemStack import ru.dbotthepony.kstarbound.item.ItemStack
import ru.dbotthepony.kstarbound.json.NativeLegacy
import ru.dbotthepony.kstarbound.util.Directives import ru.dbotthepony.kstarbound.util.Directives
import ru.dbotthepony.kstarbound.util.ExceptionLogger import ru.dbotthepony.kstarbound.util.ExceptionLogger
import ru.dbotthepony.kstarbound.util.SBPattern import ru.dbotthepony.kstarbound.util.SBPattern
@ -155,8 +157,30 @@ object Starbound : ISBFileLocator {
@JvmField @JvmField
val STRINGS: Interner<String> = interner(5) 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()) { val gson: Gson = with(GsonBuilder()) {
serializeNulls() // serializeNulls()
setDateFormat(DateFormat.LONG) setDateFormat(DateFormat.LONG)
setFieldNamingPolicy(FieldNamingPolicy.IDENTITY) setFieldNamingPolicy(FieldNamingPolicy.IDENTITY)
setPrettyPrinting() setPrettyPrinting()
@ -215,18 +239,20 @@ object Starbound : ISBFileLocator {
registerTypeAdapter(ObjectDefinition::Adapter) registerTypeAdapter(ObjectDefinition::Adapter)
registerTypeAdapter(StatModifier::Adapter) registerTypeAdapter(StatModifier::Adapter)
registerTypeAdapterFactory(NativeLegacy.Companion)
// математические классы // математические классы
registerTypeAdapter(AABBTypeAdapter) registerTypeAdapter(AABBTypeAdapter.nullSafe())
registerTypeAdapter(AABBiTypeAdapter) registerTypeAdapter(AABBiTypeAdapter.nullSafe())
registerTypeAdapter(Vector2dTypeAdapter) registerTypeAdapter(Vector2dTypeAdapter.nullSafe())
registerTypeAdapter(Vector2fTypeAdapter) registerTypeAdapter(Vector2fTypeAdapter.nullSafe())
registerTypeAdapter(Vector2iTypeAdapter) registerTypeAdapter(Vector2iTypeAdapter.nullSafe())
registerTypeAdapter(Vector3dTypeAdapter) registerTypeAdapter(Vector3dTypeAdapter.nullSafe())
registerTypeAdapter(Vector3fTypeAdapter) registerTypeAdapter(Vector3fTypeAdapter.nullSafe())
registerTypeAdapter(Vector3iTypeAdapter) registerTypeAdapter(Vector3iTypeAdapter.nullSafe())
registerTypeAdapter(Vector4iTypeAdapter) registerTypeAdapter(Vector4iTypeAdapter.nullSafe())
registerTypeAdapter(Vector4dTypeAdapter) registerTypeAdapter(Vector4dTypeAdapter.nullSafe())
registerTypeAdapter(Vector4fTypeAdapter) registerTypeAdapter(Vector4fTypeAdapter.nullSafe())
registerTypeAdapterFactory(Line2d.Companion) registerTypeAdapterFactory(Line2d.Companion)
registerTypeAdapterFactory(UniversePos.Companion) registerTypeAdapterFactory(UniversePos.Companion)
registerTypeAdapterFactory(AbstractPerlinNoise.Companion) registerTypeAdapterFactory(AbstractPerlinNoise.Companion)
@ -269,7 +295,6 @@ object Starbound : ISBFileLocator {
registerTypeAdapter(CelestialParameters::Adapter) registerTypeAdapter(CelestialParameters::Adapter)
registerTypeAdapter(Particle::Adapter) registerTypeAdapter(Particle::Adapter)
registerTypeAdapterFactory(BiomePlacementDistributionType.DATA_ADAPTER)
registerTypeAdapterFactory(BiomePlacementDistributionType.DEFINITION_ADAPTER) registerTypeAdapterFactory(BiomePlacementDistributionType.DEFINITION_ADAPTER)
registerTypeAdapterFactory(BiomePlacementItemType.DATA_ADAPTER) registerTypeAdapterFactory(BiomePlacementItemType.DATA_ADAPTER)
registerTypeAdapterFactory(BiomePlacementItemType.DEFINITION_ADAPTER) registerTypeAdapterFactory(BiomePlacementItemType.DEFINITION_ADAPTER)
@ -278,6 +303,7 @@ object Starbound : ISBFileLocator {
// register companion first, so it has lesser priority than dispatching adapter // register companion first, so it has lesser priority than dispatching adapter
registerTypeAdapterFactory(VisitableWorldParametersType.Companion) registerTypeAdapterFactory(VisitableWorldParametersType.Companion)
registerTypeAdapterFactory(VisitableWorldParametersType.ADAPTER) registerTypeAdapterFactory(VisitableWorldParametersType.ADAPTER)
registerTypeAdapter(WorldLayout.Companion)
Registries.registerAdapters(this) 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.JsonToken
import com.google.gson.stream.JsonWriter import com.google.gson.stream.JsonWriter
import ru.dbotthepony.kommons.gson.consumeNull import ru.dbotthepony.kommons.gson.consumeNull
import ru.dbotthepony.kommons.guava.immutableMap
import ru.dbotthepony.kstarbound.json.builder.JsonImplementation import ru.dbotthepony.kstarbound.json.builder.JsonImplementation
@JsonImplementation(ThingDescription::class) @JsonImplementation(ThingDescription::class)
@ -84,6 +85,13 @@ data class ThingDescription(
val EMPTY = ThingDescription() val EMPTY = ThingDescription()
} }
fun toMap(): ImmutableMap<String, String> {
return immutableMap {
putAll(racialDescription)
put("description", description)
}
}
fun fixDescription(newDescription: String): ThingDescription { fun fixDescription(newDescription: String): ThingDescription {
return copy( return copy(
shortdescription = if (shortdescription == "...") newDescription else shortdescription, shortdescription = if (shortdescription == "...") newDescription else shortdescription,

View File

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

View File

@ -1,20 +1,18 @@
package ru.dbotthepony.kstarbound.defs.world package ru.dbotthepony.kstarbound.defs.world
import com.google.common.collect.ImmutableList import com.google.common.collect.ImmutableList
import ru.dbotthepony.kstarbound.Registry import ru.dbotthepony.kstarbound.json.NativeLegacy
import ru.dbotthepony.kstarbound.defs.tile.MaterialModifier import ru.dbotthepony.kstarbound.json.builder.JsonFactory
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
@JsonFactory
data class Biome( data class Biome(
val hueShift: Double = 0.0, val hueShift: Double = 0.0,
val materialHueShift: Int = 0,
val baseName: String, val baseName: String,
val description: String, val description: String,
val mainBlock: Registry.Entry<TileDefinition>? = null, val mainBlock: NativeLegacy.Tile,
val subBlocks: ImmutableList<Registry.Entry<TileDefinition>> = ImmutableList.of(), val subBlocks: ImmutableList<NativeLegacy.Tile> = ImmutableList.of(),
val ores: ImmutableList<Pair<Registry.Entry<MaterialModifier>, Double>> = ImmutableList.of(), val ores: ImmutableList<Pair<NativeLegacy.TileMod, Double>> = ImmutableList.of(),
val musicTrack: AmbientNoisesDefinition? = null, val musicTrack: AmbientNoisesDefinition? = null,
val ambientNoises: AmbientNoisesDefinition? = null, val ambientNoises: AmbientNoisesDefinition? = null,
val surfacePlaceables: BiomePlaceables = BiomePlaceables(), 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.JsonConfigFunction
import ru.dbotthepony.kstarbound.defs.tile.MaterialModifier import ru.dbotthepony.kstarbound.defs.tile.MaterialModifier
import ru.dbotthepony.kstarbound.defs.tile.TileDefinition 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.builder.JsonFactory
import ru.dbotthepony.kstarbound.json.pairListAdapter import ru.dbotthepony.kstarbound.json.pairListAdapter
import ru.dbotthepony.kstarbound.util.random.random import ru.dbotthepony.kstarbound.util.random.random
import ru.dbotthepony.kstarbound.world.positiveModulo
import java.util.random.RandomGenerator import java.util.random.RandomGenerator
@JsonFactory @JsonFactory
@ -63,9 +65,10 @@ data class BiomeDefinition(
baseName = name, baseName = name,
description = description, description = description,
hueShift = hueShift, hueShift = hueShift,
materialHueShift = ((positiveModulo(hueShift, 360.0) / 360.0) * 255.0).toInt(),
mainBlock = mainBlock?.entry, mainBlock = NativeLegacy.Tile(mainBlock?.entry),
subBlocks = subBlocks?.stream()?.map { it.entry }?.filterNotNull()?.collect(ImmutableList.toImmutableList()) ?: ImmutableList.of(), subBlocks = subBlocks?.stream()?.map { NativeLegacy.Tile(it.entry) }?.filterNotNull()?.collect(ImmutableList.toImmutableList()) ?: ImmutableList.of(),
musicTrack = musicTrack, musicTrack = musicTrack,
ambientNoises = ambientNoises, ambientNoises = ambientNoises,
@ -77,10 +80,10 @@ data class BiomeDefinition(
ores = (ores?.value?.evaluate(threatLevel, oresAdapter)?.map { ores = (ores?.value?.evaluate(threatLevel, oresAdapter)?.map {
it.stream() it.stream()
.filter { it.second > 0.0 } .filter { it.second > 0.0 }
.map { Registries.tileModifiers[it.first] to it.second } .map { NativeLegacy.TileMod(Registries.tileModifiers.ref(it.first)) to it.second }
.filter { it.first != null } .filter { it.first.native.isPresent }
.collect(ImmutableList.toImmutableList()) .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.Gson
import com.google.gson.JsonArray import com.google.gson.JsonArray
import com.google.gson.JsonElement import com.google.gson.JsonElement
import com.google.gson.JsonObject
import com.google.gson.JsonPrimitive import com.google.gson.JsonPrimitive
import com.google.gson.JsonSyntaxException import com.google.gson.JsonSyntaxException
import com.google.gson.TypeAdapter import com.google.gson.TypeAdapter
@ -19,7 +20,9 @@ import ru.dbotthepony.kommons.gson.value
import ru.dbotthepony.kstarbound.Registry import ru.dbotthepony.kstarbound.Registry
import ru.dbotthepony.kstarbound.Starbound import ru.dbotthepony.kstarbound.Starbound
import ru.dbotthepony.kstarbound.collect.WeightedList import ru.dbotthepony.kstarbound.collect.WeightedList
import ru.dbotthepony.kstarbound.defs.PerlinNoiseParameters
import ru.dbotthepony.kstarbound.defs.tile.MaterialModifier 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.JsonFactory
import ru.dbotthepony.kstarbound.json.builder.JsonFlat import ru.dbotthepony.kstarbound.json.builder.JsonFlat
import ru.dbotthepony.kstarbound.json.listAdapter import ru.dbotthepony.kstarbound.json.listAdapter
@ -28,14 +31,14 @@ import java.util.stream.Stream
@JsonFactory @JsonFactory
data class BiomePlaceables( data class BiomePlaceables(
val grassMod: Registry.Entry<MaterialModifier>? = null, val grassMod: NativeLegacy.TileMod = NativeLegacy.TileMod(null as Registry.Ref<MaterialModifier>?),
val ceilingGrassMod: Registry.Entry<MaterialModifier>? = null, val ceilingGrassMod: NativeLegacy.TileMod = NativeLegacy.TileMod(null as Registry.Ref<MaterialModifier>?),
val grassModDensity: Double = 0.0, val grassModDensity: Double = 0.0,
val ceilingGrassModDensity: Double = 0.0, val ceilingGrassModDensity: Double = 0.0,
val items: ImmutableList<DistributionItem> = ImmutableList.of(), val itemDistributions: ImmutableList<DistributionItem> = ImmutableList.of(),
) { ) {
fun firstTreeVariant(): TreeVariant? { fun firstTreeVariant(): TreeVariant? {
return items.stream() return itemDistributions.stream()
.flatMap { it.data.itemStream() } .flatMap { it.data.itemStream() }
.map { it as? Tree } .map { it as? Tree }
.filterNotNull() .filterNotNull()
@ -107,7 +110,7 @@ data class BiomePlaceables(
)) ))
"grass" -> Grass(grassVariant.read(`in`)) "grass" -> Grass(grassVariant.read(`in`))
"bush" -> Bush(bushVariant.read(`in`)) "bush" -> Bush(bushVariant.read(`in`))
"tree" -> Tree(trees.read(`in`)) "treePair" -> Tree(trees.read(`in`))
"objectPool" -> Object(objects.read(`in`)) "objectPool" -> Object(objects.read(`in`))
else -> throw JsonSyntaxException("Unknown biome placement item $type") 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 @JsonFactory
data class RandomDistribution( data class DistributionData(
val blockSeed: Long, val distribution: BiomePlacementDistributionType,
val randomItems: ImmutableList<Item>, val blockSeed: Long = 0L,
) : DistributionData() { val blockProbability: Double = 0.0,
override val type: BiomePlacementDistributionType val randomItems: ImmutableList<Item> = ImmutableList.of(),
get() = BiomePlacementDistributionType.RANDOM val modulus: Int = 0,
val modulusOffset: Int = 0,
override fun itemStream(): Stream<Item> { val densityFunction: AbstractPerlinNoise = AbstractPerlinNoise.of(PerlinNoiseParameters()),
return randomItems.stream() val modulusDistortion: AbstractPerlinNoise = AbstractPerlinNoise.of(PerlinNoiseParameters()),
} val weightedItems: ImmutableList<Pair<Item, AbstractPerlinNoise>> = ImmutableList.of(),
} ) {
fun itemStream(): Stream<Item> {
@JsonFactory if (distribution == BiomePlacementDistributionType.RANDOM) {
data class PeriodicDistribution( return randomItems.stream()
val modulus: Int, } else {
val modulusOffset: Int, return weightedItems.stream().map { it.first }
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 }
} }
} }
} }

View File

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

View File

@ -7,16 +7,9 @@ import ru.dbotthepony.kstarbound.json.builder.IStringSerializable
enum class BiomePlacementDistributionType( enum class BiomePlacementDistributionType(
override val jsonName: String, override val jsonName: String,
val def: TypeToken<out BiomePlaceablesDefinition.DistributionData>, val def: TypeToken<out BiomePlaceablesDefinition.DistributionData>,
val data: TypeToken<out BiomePlaceables.DistributionData>,
) : IStringSerializable { ) : IStringSerializable {
RANDOM("random", RANDOM("random",TypeToken.get(BiomePlaceablesDefinition.RandomDistribution::class.java)),
TypeToken.get(BiomePlaceablesDefinition.RandomDistribution::class.java), PERIODIC("periodic", TypeToken.get(BiomePlaceablesDefinition.PeriodicDistribution::class.java));
TypeToken.get(BiomePlaceables.RandomDistribution::class.java)
),
PERIODIC("periodic",
TypeToken.get(BiomePlaceablesDefinition.PeriodicDistribution::class.java),
TypeToken.get(BiomePlaceables.PeriodicDistribution::class.java)
);
override fun match(name: String): Boolean { override fun match(name: String): Boolean {
return name.lowercase() == jsonName return name.lowercase() == jsonName
@ -24,6 +17,5 @@ enum class BiomePlacementDistributionType(
companion object { companion object {
val DEFINITION_ADAPTER = DispatchingAdapter("type", { type }, { def }, entries) 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 baseHueShift: Double,
val modHueShift: Double, val modHueShift: Double,
@JsonFlat val descriptions: ImmutableMap<String, String> = ImmutableMap.of(),
val descriptions: ThingDescription,
val ceiling: Boolean, val ceiling: Boolean,
val ephemeral: Boolean, val ephemeral: Boolean,
val tileDamageParameters: TileDamageConfig, val tileDamageParameters: TileDamageConfig,
) { ) {
@JsonFactory @JsonFactory(asList = true)
data class Shape(val image: String, val mods: ImmutableList<String>) data class Shape(val image: String, val mods: ImmutableList<String>)
@JsonFactory @JsonFactory
@ -64,7 +63,7 @@ class BushVariant(
directory = data.file?.computeDirectory() ?: "/", directory = data.file?.computeDirectory() ?: "/",
modHueShift = modHueShift, modHueShift = modHueShift,
ceiling = data.value.ceiling, 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, ephemeral = data.value.ephemeral,
tileDamageParameters = (data.value.damageTable?.value ?: GlobalDefaults.bushDamage).copy(totalHealth = data.value.health), tileDamageParameters = (data.value.damageTable?.value ?: GlobalDefaults.bushDamage).copy(totalHealth = data.value.health),
modName = modName, 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.JsonReader
import com.google.gson.stream.JsonWriter import com.google.gson.stream.JsonWriter
import ru.dbotthepony.kommons.gson.consumeNull import ru.dbotthepony.kommons.gson.consumeNull
import ru.dbotthepony.kommons.gson.set
import ru.dbotthepony.kommons.gson.value import ru.dbotthepony.kommons.gson.value
import ru.dbotthepony.kstarbound.Starbound
import ru.dbotthepony.kstarbound.json.builder.JsonFactory import ru.dbotthepony.kstarbound.json.builder.JsonFactory
import ru.dbotthepony.kstarbound.util.random.random import ru.dbotthepony.kstarbound.util.random.random
import ru.dbotthepony.kstarbound.world.UniversePos import ru.dbotthepony.kstarbound.world.UniversePos

View File

@ -1,5 +1,6 @@
package ru.dbotthepony.kstarbound.defs.world package ru.dbotthepony.kstarbound.defs.world
import com.google.common.collect.ImmutableMap
import com.google.common.collect.ImmutableSet import com.google.common.collect.ImmutableSet
import com.google.gson.JsonElement import com.google.gson.JsonElement
import ru.dbotthepony.kstarbound.GlobalDefaults import ru.dbotthepony.kstarbound.GlobalDefaults
@ -17,8 +18,7 @@ data class GrassVariant(
val directory: String, val directory: String,
val images: ImmutableSet<String> = ImmutableSet.of(), val images: ImmutableSet<String> = ImmutableSet.of(),
val hueShift: Double, val hueShift: Double,
@JsonFlat val descriptions: ImmutableMap<String, String> = ImmutableMap.of(),
val descriptions: ThingDescription,
val ceiling: Boolean, val ceiling: Boolean,
val ephemeral: Boolean, val ephemeral: Boolean,
val tileDamageParameters: TileDamageConfig, val tileDamageParameters: TileDamageConfig,
@ -53,7 +53,7 @@ data class GrassVariant(
ceiling = data.value.ceiling, ceiling = data.value.ceiling,
ephemeral = data.value.ephemeral, ephemeral = data.value.ephemeral,
hueShift = hueShift, 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) 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 com.google.common.collect.ImmutableList
import ru.dbotthepony.kommons.collect.filterNotNull import ru.dbotthepony.kommons.collect.filterNotNull
import ru.dbotthepony.kommons.gson.get
import ru.dbotthepony.kommons.math.RGBAColor import ru.dbotthepony.kommons.math.RGBAColor
import ru.dbotthepony.kommons.util.Either import ru.dbotthepony.kommons.util.Either
import ru.dbotthepony.kommons.vector.Vector2d import ru.dbotthepony.kommons.vector.Vector2d
@ -80,7 +81,7 @@ class Parallax(
if (isFoliage) { if (isFoliage) {
if (treeVariant == null) return null 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/", "")}/" texPath = "${treeVariant.foliageDirectory}/parallax/${kind.replace("foliage/", "")}/"
} else if (isStem) { } else if (isStem) {
if (treeVariant == null) return null if (treeVariant == null) return null

View File

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

View File

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

View File

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

View File

@ -4,12 +4,17 @@ import com.google.common.collect.ImmutableList
import com.google.gson.JsonArray import com.google.gson.JsonArray
import com.google.gson.JsonObject import com.google.gson.JsonObject
import com.google.gson.JsonPrimitive import com.google.gson.JsonPrimitive
import com.google.gson.TypeAdapter
import com.google.gson.reflect.TypeToken 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.doubles.DoubleArrayList
import it.unimi.dsi.fastutil.ints.IntArrayList import it.unimi.dsi.fastutil.ints.IntArrayList
import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet
import ru.dbotthepony.kommons.gson.JsonArrayCollector import ru.dbotthepony.kommons.gson.JsonArrayCollector
import ru.dbotthepony.kommons.gson.consumeNull
import ru.dbotthepony.kommons.gson.set import ru.dbotthepony.kommons.gson.set
import ru.dbotthepony.kommons.gson.value
import ru.dbotthepony.kommons.math.RGBAColor import ru.dbotthepony.kommons.math.RGBAColor
import ru.dbotthepony.kommons.util.AABBi import ru.dbotthepony.kommons.util.AABBi
import ru.dbotthepony.kommons.util.Either import ru.dbotthepony.kommons.util.Either
@ -202,11 +207,11 @@ class WorldLayout {
val layers: JsonArray, val layers: JsonArray,
) )
fun toJson(isLegacy: Boolean): JsonObject { fun toJson(): JsonObject {
return Starbound.gson.toJsonTree(SerializedForm( return Starbound.gson.toJsonTree(SerializedForm(
worldSize, regionBlending, blockNoise, blendNoise, worldSize, regionBlending, blockNoise, blendNoise,
playerStartSearchRegions, biomes.list, terrainSelectors.list, 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 )) as JsonObject
} }
@ -410,4 +415,24 @@ class WorldLayout {
biome.parallax?.fadeToSkyColor(skyColoring) 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.common.collect.ImmutableMap
import com.google.gson.JsonElement import com.google.gson.JsonElement
import com.google.gson.JsonNull import com.google.gson.JsonNull
import com.google.gson.JsonObject
import ru.dbotthepony.kommons.util.AABBi import ru.dbotthepony.kommons.util.AABBi
import ru.dbotthepony.kommons.util.Either import ru.dbotthepony.kommons.util.Either
import ru.dbotthepony.kommons.vector.Vector2d import ru.dbotthepony.kommons.vector.Vector2d
@ -15,7 +16,7 @@ import ru.dbotthepony.kstarbound.world.Direction1D
data class WorldStructure( data class WorldStructure(
val region: AABBi = AABBi(Vector2i.ZERO, Vector2i.ZERO), val region: AABBi = AABBi(Vector2i.ZERO, Vector2i.ZERO),
val anchorPosition: Vector2i = Vector2i.ZERO, val anchorPosition: Vector2i = Vector2i.ZERO,
val config: JsonElement = JsonNull.INSTANCE, var config: JsonElement = JsonObject(),
val backgroundOverlays: ImmutableList<Overlay> = ImmutableList.of(), val backgroundOverlays: ImmutableList<Overlay> = ImmutableList.of(),
val foregroundOverlays: ImmutableList<Overlay> = ImmutableList.of(), val foregroundOverlays: ImmutableList<Overlay> = ImmutableList.of(),
val backgroundBlocks: ImmutableList<Block> = ImmutableList.of(), val backgroundBlocks: ImmutableList<Block> = ImmutableList.of(),
@ -23,6 +24,13 @@ data class WorldStructure(
val objects: ImmutableList<Obj> = ImmutableList.of(), val objects: ImmutableList<Obj> = ImmutableList.of(),
val flaggedBlocks: ImmutableMap<String, ImmutableList<Vector2i>> = ImmutableMap.of(), val flaggedBlocks: ImmutableMap<String, ImmutableList<Vector2i>> = ImmutableMap.of(),
) { ) {
init {
if (config == JsonNull.INSTANCE) {
// so it is not omitted
config = JsonObject()
}
}
@JsonFactory @JsonFactory
data class Overlay(val min: Vector2d, val image: String, val fullbright: Boolean) 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 skyParameters: SkyParameters = SkyParameters(),
val seed: Long = 0L, val seed: Long = 0L,
val size: Either<WorldGeometry, Vector2i>, val size: Either<WorldGeometry, Vector2i>,
val regionData: JsonElement = JsonNull.INSTANCE, val regionData: WorldLayout? = null,
//val customTerrainRegions: //val customTerrainRegions:
) )
fun toJson(isLegacy: Boolean): JsonObject { fun toJson(): JsonObject {
val data = Starbound.gson.toJsonTree(SerializedForm( val data = Starbound.gson.toJsonTree(SerializedForm(
celestialParameters, worldParameters, skyParameters, seed, 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 )) as JsonObject
data["regionData"] = worldLayout?.toJson(isLegacy) ?: JsonNull.INSTANCE
return data return data
} }
@ -91,7 +91,7 @@ class WorldTemplate(val geometry: WorldGeometry) {
template.worldParameters = load.worldParameters template.worldParameters = load.worldParameters
template.skyParameters = load.skyParameters template.skyParameters = load.skyParameters
template.seed = load.seed template.seed = load.seed
template.worldLayout = load.regionData.let { if (it is JsonObject) WorldLayout().fromJson(it) else null } template.worldLayout = load.regionData
template.determineName() 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 return
} }
out.beginObject() if (asJsonArray)
out.beginArray()
else
out.beginObject()
for (type in types) { for (type in types) {
if (type.isFlat) { if (type.isFlat) {
check(!asJsonArray)
val (field, adapter) = type val (field, adapter) = type
val result = (adapter as TypeAdapter<Any>).toJsonTree((field as KProperty1<T, Any>).get(value)) 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}") 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 { } else {
val (field, adapter) = type val (field, adapter) = type
out.name(field.name)
if (!asJsonArray)
out.name(field.name)
@Suppress("unchecked_cast") @Suppress("unchecked_cast")
(adapter as TypeAdapter<Any>).write(out, (field as KProperty1<T, Any>).get(value)) (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? { 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() queue.clear()
if (isInterpolating) {
queue.add(currentTime to value)
}
} }
override fun writeInitial(data: DataOutputStream, isLegacy: Boolean) { override fun writeInitial(data: DataOutputStream, isLegacy: Boolean) {

View File

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

View File

@ -72,7 +72,7 @@ class ServerWorld private constructor(
player.skyVersion = skyVersion player.skyVersion = skyVersion
player.send(WorldStartPacket( player.send(WorldStartPacket(
templateData = template.toJson(true), templateData = Starbound.writeLegacyJson { template.toJson() },
skyData = skyData.toByteArray(), skyData = skyData.toByteArray(),
weatherData = ByteArray(0), weatherData = ByteArray(0),
playerStart = playerSpawnPosition, playerStart = playerSpawnPosition,
@ -86,7 +86,9 @@ class ServerWorld private constructor(
localInterpolationMode = false, localInterpolationMode = false,
)) ))
player.sendAndFlush(CentralStructureUpdatePacket(Starbound.gson.toJsonTree(centralStructure))) Starbound.writeLegacyJson {
player.sendAndFlush(CentralStructureUpdatePacket(Starbound.gson.toJsonTree(centralStructure)))
}
} else { } else {
player.sendAndFlush(JoinWorldPacket(this)) player.sendAndFlush(JoinWorldPacket(this))
} }
@ -97,7 +99,9 @@ class ServerWorld private constructor(
unpause() unpause()
try { 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) { } catch (err: RejectedExecutionException) {
return CompletableFuture.failedFuture(err) return CompletableFuture.failedFuture(err)
} }
@ -123,7 +127,10 @@ class ServerWorld private constructor(
check(!isClosed.get()) { "$this is invalid" } check(!isClosed.get()) { "$this is invalid" }
try { 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) { } catch (err: RejectedExecutionException) {
return CompletableFuture.completedFuture(false) return CompletableFuture.completedFuture(false)
} }

View File

@ -20,7 +20,7 @@ object AssetPathStack {
push(value.substringBefore(':').substringBeforeLast('/')) push(value.substringBefore(':').substringBeforeLast('/'))
} }
fun last() = stack.lastOrNull() fun last() = stack.lastOrNull() ?: "/"
fun pop() = stack.removeLast() fun pop() = stack.removeLast()
inline fun <T> block(path: String, block: (String) -> T): T { inline fun <T> block(path: String, block: (String) -> T): T {
@ -44,11 +44,11 @@ object AssetPathStack {
} }
fun remap(path: String): String { 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 { fun remapSafe(path: String): String {
return remap(last() ?: return path, path) return remap(last(), path)
} }
fun relativeTo(base: String, path: String): String { 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) } protected val g3 by lazy { Double2DArray.allocate(parameters.scale * 2 + 2, 3) }
init { init {
if (parameters.seed != null) { if (parameters.seed != null && parameters.type != PerlinNoiseParameters.Type.UNITIALIZED) {
init(parameters.seed) init(parameters.seed)
} }
} }
fun init(seed: Long) { fun init(seed: Long) {
if (parameters.type == PerlinNoiseParameters.Type.UNITIALIZED)
return
isInitialized = true isInitialized = true
this.seed = seed this.seed = seed
@ -218,6 +221,7 @@ abstract class AbstractPerlinNoise(val parameters: PerlinNoiseParameters) {
PerlinNoiseParameters.Type.PERLIN -> PerlinNoise(parameters) PerlinNoiseParameters.Type.PERLIN -> PerlinNoise(parameters)
PerlinNoiseParameters.Type.BILLOW -> BillowNoise(parameters) PerlinNoiseParameters.Type.BILLOW -> BillowNoise(parameters)
PerlinNoiseParameters.Type.RIDGED_MULTI -> RidgedNoise(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
}
}