WorldTemplate, WorldLayout, VisitableWorldParameters, Biomes, BiomeDefinitions, Trees, Grass, Bushes, Terrain Selectors
RenderDirectives, JsonRPC, Json adapters fixes, DispatchingTypeAdapter
This commit is contained in:
parent
73cf5f596c
commit
d37bad79c6
20
ADDITIONS.md
Normal file
20
ADDITIONS.md
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
|
||||||
|
## JSON additions
|
||||||
|
|
||||||
|
### Worldgen
|
||||||
|
* Where applicable, Perlin noise now can have custom seed specified
|
||||||
|
* Change above allows to explicitly specify universe seed (as celestial.config:systemTypePerlin:seed)
|
||||||
|
* Perlin noise now can be of arbitrary scale (defaults to 512, specified with 'scale' key, integer type, >=16)
|
||||||
|
|
||||||
|
#### Terrain
|
||||||
|
* Nested terrain selectors now get their unique seeds (displacement selector can now properly be nested inside other displacement selector)
|
||||||
|
* Previously, all nested terrain selectors were based off the same seed
|
||||||
|
* displacement terrain selector has xClamp added, works like yClamp
|
||||||
|
|
||||||
|
#### Biomes
|
||||||
|
* Tree biome placeables now have `variantsRange` (defaults to `[1, 1]`) and `subVariantsRange` (defaults to `[2, 2]`)
|
||||||
|
* `variantsRange` is responsible for "stem-foliage" combinations
|
||||||
|
* `subVariantsRange` is responsible for "stem-foliage" hue shift combinations
|
||||||
|
* Rolled per each "stem-foliage" combination
|
||||||
|
* Also two more properties were added: `sameStemHueShift` (defaults to `true`) and `sameFoliageHueShift` (defaults to `false`), which fixate hue shifts within same "stem-foliage" combination
|
||||||
|
* Original engine always generates two tree types when processing placeable items, new engine however, allows to generate any number of trees.
|
@ -13,6 +13,8 @@ version = "0.1-SNAPSHOT"
|
|||||||
|
|
||||||
val lwjglVersion: String by project
|
val lwjglVersion: String by project
|
||||||
val lwjglNatives: String by project
|
val lwjglNatives: String by project
|
||||||
|
val kotlinVersion: String by project
|
||||||
|
val kotlinCoroutinesVersion: String by project
|
||||||
|
|
||||||
repositories {
|
repositories {
|
||||||
mavenCentral()
|
mavenCentral()
|
||||||
@ -39,8 +41,9 @@ tasks.compileKotlin {
|
|||||||
dependencies {
|
dependencies {
|
||||||
val kommonsVersion: String by project
|
val kommonsVersion: String by project
|
||||||
|
|
||||||
implementation("org.jetbrains.kotlin:kotlin-stdlib:1.9.10")
|
implementation("org.jetbrains.kotlin:kotlin-stdlib:$kotlinVersion")
|
||||||
implementation("org.jetbrains.kotlin:kotlin-reflect:1.9.10")
|
implementation("org.jetbrains.kotlin:kotlin-reflect:$kotlinVersion")
|
||||||
|
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:$kotlinCoroutinesVersion")
|
||||||
|
|
||||||
implementation("org.apache.logging.log4j:log4j-api:2.17.1")
|
implementation("org.apache.logging.log4j:log4j-api:2.17.1")
|
||||||
implementation("org.apache.logging.log4j:log4j-core:2.17.1")
|
implementation("org.apache.logging.log4j:log4j-core:2.17.1")
|
||||||
|
@ -1,8 +1,9 @@
|
|||||||
kotlin.code.style=official
|
kotlin.code.style=official
|
||||||
org.gradle.jvmargs=-Xmx2g -XX:MaxMetaspaceSize=512m
|
org.gradle.jvmargs=-Xmx2g -XX:MaxMetaspaceSize=512m
|
||||||
|
|
||||||
kotlinVersion=1.9.0
|
kotlinVersion=1.9.10
|
||||||
kommonsVersion=2.7.16
|
kotlinCoroutinesVersion=1.8.0
|
||||||
|
kommonsVersion=2.9.20
|
||||||
|
|
||||||
ffiVersion=2.2.13
|
ffiVersion=2.2.13
|
||||||
lwjglVersion=3.3.0
|
lwjglVersion=3.3.0
|
||||||
|
@ -1,13 +1,23 @@
|
|||||||
package ru.dbotthepony.kstarbound
|
package ru.dbotthepony.kstarbound
|
||||||
|
|
||||||
|
import com.google.common.collect.ImmutableMap
|
||||||
|
import com.google.gson.TypeAdapter
|
||||||
import org.apache.logging.log4j.LogManager
|
import org.apache.logging.log4j.LogManager
|
||||||
import ru.dbotthepony.kstarbound.defs.ActorMovementParameters
|
import ru.dbotthepony.kstarbound.defs.ActorMovementParameters
|
||||||
import ru.dbotthepony.kstarbound.defs.ClientConfigParameters
|
import ru.dbotthepony.kstarbound.defs.ClientConfigParameters
|
||||||
import ru.dbotthepony.kstarbound.defs.MovementParameters
|
import ru.dbotthepony.kstarbound.defs.MovementParameters
|
||||||
|
import ru.dbotthepony.kstarbound.defs.tile.TileDamageConfig
|
||||||
|
import ru.dbotthepony.kstarbound.defs.world.TerrestrialWorldsConfig
|
||||||
|
import ru.dbotthepony.kstarbound.defs.world.AsteroidWorldsConfig
|
||||||
|
import ru.dbotthepony.kstarbound.defs.world.DungeonWorldsConfig
|
||||||
|
import ru.dbotthepony.kstarbound.defs.world.WorldTemplateConfig
|
||||||
|
import ru.dbotthepony.kstarbound.json.mapAdapter
|
||||||
|
import ru.dbotthepony.kstarbound.json.pairSetAdapter
|
||||||
import ru.dbotthepony.kstarbound.util.AssetPathStack
|
import ru.dbotthepony.kstarbound.util.AssetPathStack
|
||||||
import java.util.concurrent.ExecutorService
|
import java.util.concurrent.ExecutorService
|
||||||
import java.util.concurrent.ForkJoinTask
|
import java.util.concurrent.ForkJoinTask
|
||||||
import java.util.concurrent.Future
|
import java.util.concurrent.Future
|
||||||
|
import kotlin.properties.Delegates
|
||||||
import kotlin.reflect.KMutableProperty0
|
import kotlin.reflect.KMutableProperty0
|
||||||
|
|
||||||
object GlobalDefaults {
|
object GlobalDefaults {
|
||||||
@ -22,6 +32,27 @@ object GlobalDefaults {
|
|||||||
var clientParameters = ClientConfigParameters()
|
var clientParameters = ClientConfigParameters()
|
||||||
private set
|
private set
|
||||||
|
|
||||||
|
var worldTemplate by Delegates.notNull<WorldTemplateConfig>()
|
||||||
|
private set
|
||||||
|
|
||||||
|
var terrestrialWorlds by Delegates.notNull<TerrestrialWorldsConfig>()
|
||||||
|
private set
|
||||||
|
|
||||||
|
var asteroidWorlds by Delegates.notNull<AsteroidWorldsConfig>()
|
||||||
|
private set
|
||||||
|
|
||||||
|
var dungeonWorlds by Delegates.notNull<ImmutableMap<String, DungeonWorldsConfig>>()
|
||||||
|
private set
|
||||||
|
|
||||||
|
var grassDamage by Delegates.notNull<TileDamageConfig>()
|
||||||
|
private set
|
||||||
|
|
||||||
|
var treeDamage by Delegates.notNull<TileDamageConfig>()
|
||||||
|
private set
|
||||||
|
|
||||||
|
var bushDamage by Delegates.notNull<TileDamageConfig>()
|
||||||
|
private set
|
||||||
|
|
||||||
private object EmptyTask : ForkJoinTask<Unit>() {
|
private object EmptyTask : ForkJoinTask<Unit>() {
|
||||||
private fun readResolve(): Any = EmptyTask
|
private fun readResolve(): Any = EmptyTask
|
||||||
override fun getRawResult() {
|
override fun getRawResult() {
|
||||||
@ -35,30 +66,44 @@ object GlobalDefaults {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private inline fun <reified T> load(path: String, accept: KMutableProperty0<T>, executor: ExecutorService): Future<*> {
|
private fun <T> load(path: String, accept: KMutableProperty0<T>, adapter: TypeAdapter<T>): Future<*> {
|
||||||
val file = Starbound.locate(path)
|
val file = Starbound.loadJsonAsset(path)
|
||||||
|
|
||||||
if (!file.exists) {
|
if (file == null) {
|
||||||
LOGGER.fatal("$path does not exist, expect bad things to happen!")
|
LOGGER.fatal("$path does not exist or is not a file, expect bad things to happen!")
|
||||||
return EmptyTask
|
|
||||||
} else if (!file.isFile) {
|
|
||||||
LOGGER.fatal("$path is not a file, expect bad things to happen!")
|
|
||||||
return EmptyTask
|
return EmptyTask
|
||||||
} else {
|
} else {
|
||||||
return executor.submit {
|
return Starbound.EXECUTOR.submit {
|
||||||
AssetPathStack("/") {
|
try {
|
||||||
accept.set(Starbound.gson.fromJson(file.jsonReader(), T::class.java))
|
AssetPathStack("/") {
|
||||||
|
accept.set(adapter.fromJsonTree(file))
|
||||||
|
}
|
||||||
|
} catch (err: Throwable) {
|
||||||
|
LOGGER.fatal("Error while reading $path, expect bad things to happen!", err)
|
||||||
|
throw err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun load(executor: ExecutorService): List<Future<*>> {
|
private inline fun <reified T> load(path: String, accept: KMutableProperty0<T>): Future<*> {
|
||||||
|
return load(path, accept, Starbound.gson.getAdapter(T::class.java))
|
||||||
|
}
|
||||||
|
|
||||||
|
fun load(): List<Future<*>> {
|
||||||
val tasks = ArrayList<Future<*>>()
|
val tasks = ArrayList<Future<*>>()
|
||||||
|
|
||||||
tasks.add(load("/default_actor_movement.config", ::actorMovementParameters, executor))
|
tasks.add(load("/default_actor_movement.config", ::actorMovementParameters))
|
||||||
tasks.add(load("/default_movement.config", ::movementParameters, executor))
|
tasks.add(load("/default_movement.config", ::movementParameters))
|
||||||
tasks.add(load("/client.config", ::clientParameters, executor))
|
tasks.add(load("/client.config", ::clientParameters))
|
||||||
|
tasks.add(load("/terrestrial_worlds.config", ::terrestrialWorlds))
|
||||||
|
tasks.add(load("/asteroids_worlds.config", ::asteroidWorlds))
|
||||||
|
tasks.add(load("/world_template.config", ::worldTemplate))
|
||||||
|
tasks.add(load("/dungeon_worlds.config", ::dungeonWorlds, Starbound.gson.mapAdapter()))
|
||||||
|
|
||||||
|
tasks.add(load("/plants/grassDamage.config", ::grassDamage))
|
||||||
|
tasks.add(load("/plants/treeDamage.config", ::treeDamage))
|
||||||
|
tasks.add(load("/plants/bushDamage.config", ::bushDamage))
|
||||||
|
|
||||||
return tasks
|
return tasks
|
||||||
}
|
}
|
||||||
|
@ -1,14 +1,17 @@
|
|||||||
package ru.dbotthepony.kstarbound
|
package ru.dbotthepony.kstarbound
|
||||||
|
|
||||||
import it.unimi.dsi.fastutil.floats.FloatArrayList
|
import kotlinx.coroutines.async
|
||||||
|
import kotlinx.coroutines.future.asCompletableFuture
|
||||||
|
import kotlinx.coroutines.future.future
|
||||||
|
import kotlinx.coroutines.runBlocking
|
||||||
import org.apache.logging.log4j.LogManager
|
import org.apache.logging.log4j.LogManager
|
||||||
import org.lwjgl.Version
|
import org.lwjgl.Version
|
||||||
import ru.dbotthepony.kommons.io.BTreeDB6
|
|
||||||
import ru.dbotthepony.kommons.io.ByteKey
|
import ru.dbotthepony.kommons.io.ByteKey
|
||||||
import ru.dbotthepony.kommons.util.AABBi
|
import ru.dbotthepony.kommons.util.AABBi
|
||||||
import ru.dbotthepony.kommons.vector.Vector2d
|
import ru.dbotthepony.kommons.vector.Vector2d
|
||||||
import ru.dbotthepony.kommons.vector.Vector2i
|
import ru.dbotthepony.kommons.vector.Vector2i
|
||||||
import ru.dbotthepony.kstarbound.client.StarboundClient
|
import ru.dbotthepony.kstarbound.client.StarboundClient
|
||||||
|
import ru.dbotthepony.kstarbound.defs.world.VisitableWorldParameters
|
||||||
import ru.dbotthepony.kstarbound.io.BTreeDB5
|
import ru.dbotthepony.kstarbound.io.BTreeDB5
|
||||||
import ru.dbotthepony.kstarbound.server.IntegratedStarboundServer
|
import ru.dbotthepony.kstarbound.server.IntegratedStarboundServer
|
||||||
import ru.dbotthepony.kstarbound.server.world.LegacyChunkSource
|
import ru.dbotthepony.kstarbound.server.world.LegacyChunkSource
|
||||||
@ -23,7 +26,6 @@ import java.io.DataInputStream
|
|||||||
import java.io.File
|
import java.io.File
|
||||||
import java.net.InetSocketAddress
|
import java.net.InetSocketAddress
|
||||||
import java.util.*
|
import java.util.*
|
||||||
import java.util.concurrent.Executor
|
|
||||||
import java.util.concurrent.TimeUnit
|
import java.util.concurrent.TimeUnit
|
||||||
import java.util.zip.Inflater
|
import java.util.zip.Inflater
|
||||||
import java.util.zip.InflaterInputStream
|
import java.util.zip.InflaterInputStream
|
||||||
@ -38,10 +40,28 @@ fun main() {
|
|||||||
val data = ServerUniverse()
|
val data = ServerUniverse()
|
||||||
|
|
||||||
val t = System.nanoTime()
|
val t = System.nanoTime()
|
||||||
val result = data.scanConstellationLines(AABBi(Vector2i(-100, -100), Vector2i(100, 100))).get()
|
val result = Starbound.COROUTINES.future {
|
||||||
|
val systems = data.scanSystems(AABBi(Vector2i(-50, -50), Vector2i(50, 50)), setOf("whitestar"))
|
||||||
|
|
||||||
|
for (system in systems) {
|
||||||
|
for (children in data.children(system)) {
|
||||||
|
if (children.isPlanet) {
|
||||||
|
val params = data.parameters(children)!!
|
||||||
|
|
||||||
|
if (params.visitableParameters != null) {
|
||||||
|
//val write = params.visitableParameters!!.toJson(false)
|
||||||
|
//println(write)
|
||||||
|
//println(Starbound.gson.fromJson(write, VisitableWorldParameters::class.java))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
systems
|
||||||
|
}.get()
|
||||||
|
|
||||||
println(System.nanoTime() - t)
|
println(System.nanoTime() - t)
|
||||||
|
|
||||||
println(result)
|
|
||||||
data.close()
|
data.close()
|
||||||
|
|
||||||
return
|
return
|
||||||
@ -74,8 +94,8 @@ fun main() {
|
|||||||
val server = IntegratedStarboundServer(File("./"))
|
val server = IntegratedStarboundServer(File("./"))
|
||||||
val client = StarboundClient.create().get()
|
val client = StarboundClient.create().get()
|
||||||
//val client2 = StarboundClient.create().get()
|
//val client2 = StarboundClient.create().get()
|
||||||
val world = ServerWorld(server, 0L, WorldGeometry(Vector2i(3000, 2000), true, false))
|
val world = ServerWorld(server, WorldGeometry(Vector2i(3000, 2000), true, false))
|
||||||
world.addChunkSource(LegacyChunkSource(db))
|
world.addChunkSource(LegacyChunkSource.file(db))
|
||||||
world.thread.start()
|
world.thread.start()
|
||||||
|
|
||||||
//Starbound.addFilePath(File("./unpacked_assets/"))
|
//Starbound.addFilePath(File("./unpacked_assets/"))
|
||||||
|
@ -67,14 +67,14 @@ object RecipeRegistry {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun load(fileTree: Map<String, List<IStarboundFile>>, executor: ExecutorService): List<Future<*>> {
|
fun load(fileTree: Map<String, List<IStarboundFile>>): List<Future<*>> {
|
||||||
val files = fileTree["recipe"] ?: return emptyList()
|
val files = fileTree["recipe"] ?: return emptyList()
|
||||||
|
|
||||||
val elements = Starbound.gson.getAdapter(JsonElement::class.java)
|
val elements = Starbound.gson.getAdapter(JsonElement::class.java)
|
||||||
val recipes = Starbound.gson.getAdapter(RecipeDefinition::class.java)
|
val recipes = Starbound.gson.getAdapter(RecipeDefinition::class.java)
|
||||||
|
|
||||||
return files.map { listedFile ->
|
return files.map { listedFile ->
|
||||||
executor.submit {
|
Starbound.EXECUTOR.submit {
|
||||||
try {
|
try {
|
||||||
val json = elements.read(listedFile.jsonReader())
|
val json = elements.read(listedFile.jsonReader())
|
||||||
val value = recipes.fromJsonTree(json)
|
val value = recipes.fromJsonTree(json)
|
||||||
|
@ -1,11 +1,14 @@
|
|||||||
package ru.dbotthepony.kstarbound
|
package ru.dbotthepony.kstarbound
|
||||||
|
|
||||||
|
import com.google.gson.GsonBuilder
|
||||||
import com.google.gson.JsonElement
|
import com.google.gson.JsonElement
|
||||||
import com.google.gson.JsonObject
|
import com.google.gson.JsonObject
|
||||||
import com.google.gson.internal.bind.JsonTreeReader
|
import com.google.gson.TypeAdapter
|
||||||
|
import com.google.gson.TypeAdapterFactory
|
||||||
import com.google.gson.stream.JsonReader
|
import com.google.gson.stream.JsonReader
|
||||||
import org.apache.logging.log4j.LogManager
|
import org.apache.logging.log4j.LogManager
|
||||||
import ru.dbotthepony.kstarbound.defs.Json2Function
|
import ru.dbotthepony.kstarbound.defs.Json2Function
|
||||||
|
import ru.dbotthepony.kstarbound.defs.JsonConfigFunction
|
||||||
import ru.dbotthepony.kstarbound.defs.JsonFunction
|
import ru.dbotthepony.kstarbound.defs.JsonFunction
|
||||||
import ru.dbotthepony.kstarbound.defs.Species
|
import ru.dbotthepony.kstarbound.defs.Species
|
||||||
import ru.dbotthepony.kstarbound.defs.StatusEffectDefinition
|
import ru.dbotthepony.kstarbound.defs.StatusEffectDefinition
|
||||||
@ -33,33 +36,56 @@ import ru.dbotthepony.kstarbound.defs.quest.QuestTemplate
|
|||||||
import ru.dbotthepony.kstarbound.defs.tile.LiquidDefinition
|
import ru.dbotthepony.kstarbound.defs.tile.LiquidDefinition
|
||||||
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.defs.world.BushVariant
|
||||||
|
import ru.dbotthepony.kstarbound.defs.world.GrassVariant
|
||||||
|
import ru.dbotthepony.kstarbound.defs.world.TreeVariant
|
||||||
|
import ru.dbotthepony.kstarbound.defs.world.terrain.BiomeDefinition
|
||||||
|
import ru.dbotthepony.kstarbound.defs.world.terrain.TerrainSelectorFactory
|
||||||
|
import ru.dbotthepony.kstarbound.defs.world.terrain.TerrainSelectorType
|
||||||
import ru.dbotthepony.kstarbound.util.AssetPathStack
|
import ru.dbotthepony.kstarbound.util.AssetPathStack
|
||||||
|
import java.util.*
|
||||||
|
import java.util.concurrent.CompletableFuture
|
||||||
import java.util.concurrent.ExecutorService
|
import java.util.concurrent.ExecutorService
|
||||||
import java.util.concurrent.Future
|
import java.util.concurrent.Future
|
||||||
|
import java.util.function.Supplier
|
||||||
|
import kotlin.collections.ArrayList
|
||||||
|
|
||||||
object Registries {
|
object Registries {
|
||||||
private val LOGGER = LogManager.getLogger()
|
private val LOGGER = LogManager.getLogger()
|
||||||
|
|
||||||
private val registries = ArrayList<Registry<*>>()
|
private val registriesInternal = ArrayList<Registry<*>>()
|
||||||
|
val registries: List<Registry<*>> = Collections.unmodifiableList(registriesInternal)
|
||||||
|
private val adapters = ArrayList<TypeAdapterFactory>()
|
||||||
|
|
||||||
val tiles = Registry<TileDefinition>("tiles").also(registries::add)
|
fun registerAdapters(gsonBuilder: GsonBuilder) {
|
||||||
val tileModifiers = Registry<MaterialModifier>("tile modifiers").also(registries::add)
|
adapters.forEach { gsonBuilder.registerTypeAdapterFactory(it) }
|
||||||
val liquid = Registry<LiquidDefinition>("liquid").also(registries::add)
|
}
|
||||||
val species = Registry<Species>("species").also(registries::add)
|
|
||||||
val statusEffects = Registry<StatusEffectDefinition>("status effects").also(registries::add)
|
val tiles = Registry<TileDefinition>("tiles").also(registriesInternal::add).also { adapters.add(it.adapter()) }
|
||||||
val particles = Registry<ParticleDefinition>("particles").also(registries::add)
|
val tileModifiers = Registry<MaterialModifier>("tile modifiers").also(registriesInternal::add).also { adapters.add(it.adapter()) }
|
||||||
val items = Registry<IItemDefinition>("items").also(registries::add)
|
val liquid = Registry<LiquidDefinition>("liquid").also(registriesInternal::add).also { adapters.add(it.adapter()) }
|
||||||
val questTemplates = Registry<QuestTemplate>("quest templates").also(registries::add)
|
val species = Registry<Species>("species").also(registriesInternal::add).also { adapters.add(it.adapter()) }
|
||||||
val techs = Registry<TechDefinition>("techs").also(registries::add)
|
val statusEffects = Registry<StatusEffectDefinition>("status effect").also(registriesInternal::add).also { adapters.add(it.adapter()) }
|
||||||
val jsonFunctions = Registry<JsonFunction>("json functions").also(registries::add)
|
val particles = Registry<ParticleDefinition>("particle").also(registriesInternal::add).also { adapters.add(it.adapter()) }
|
||||||
val json2Functions = Registry<Json2Function>("json 2functions").also(registries::add)
|
val items = Registry<IItemDefinition>("item").also(registriesInternal::add).also { adapters.add(it.adapter()) }
|
||||||
val npcTypes = Registry<NpcTypeDefinition>("npc types").also(registries::add)
|
val questTemplates = Registry<QuestTemplate>("quest template").also(registriesInternal::add).also { adapters.add(it.adapter()) }
|
||||||
val projectiles = Registry<ProjectileDefinition>("projectiles").also(registries::add)
|
val techs = Registry<TechDefinition>("tech").also(registriesInternal::add).also { adapters.add(it.adapter()) }
|
||||||
val tenants = Registry<TenantDefinition>("tenants").also(registries::add)
|
val jsonFunctions = Registry<JsonFunction>("json function").also(registriesInternal::add).also { adapters.add(it.adapter()) }
|
||||||
val treasurePools = Registry<TreasurePoolDefinition>("treasure pools").also(registries::add)
|
val json2Functions = Registry<Json2Function>("json 2function").also(registriesInternal::add).also { adapters.add(it.adapter()) }
|
||||||
val monsterSkills = Registry<MonsterSkillDefinition>("monster skills").also(registries::add)
|
val jsonConfigFunctions = Registry<JsonConfigFunction>("json config function").also(registriesInternal::add).also { adapters.add(it.adapter()) }
|
||||||
val monsterTypes = Registry<MonsterTypeDefinition>("monster types").also(registries::add)
|
val npcTypes = Registry<NpcTypeDefinition>("npc type").also(registriesInternal::add).also { adapters.add(it.adapter()) }
|
||||||
val worldObjects = Registry<ObjectDefinition>("objects").also(registries::add)
|
val projectiles = Registry<ProjectileDefinition>("projectile").also(registriesInternal::add).also { adapters.add(it.adapter()) }
|
||||||
|
val tenants = Registry<TenantDefinition>("tenant").also(registriesInternal::add).also { adapters.add(it.adapter()) }
|
||||||
|
val treasurePools = Registry<TreasurePoolDefinition>("treasure pool").also(registriesInternal::add).also { adapters.add(it.adapter()) }
|
||||||
|
val monsterSkills = Registry<MonsterSkillDefinition>("monster skill").also(registriesInternal::add).also { adapters.add(it.adapter()) }
|
||||||
|
val monsterTypes = Registry<MonsterTypeDefinition>("monster type").also(registriesInternal::add).also { adapters.add(it.adapter()) }
|
||||||
|
val worldObjects = Registry<ObjectDefinition>("world object").also(registriesInternal::add).also { adapters.add(it.adapter()) }
|
||||||
|
val biomes = Registry<BiomeDefinition>("biome").also(registriesInternal::add).also { adapters.add(it.adapter()) }
|
||||||
|
val terrainSelectors = Registry<TerrainSelectorFactory<*, *>>("terrain selector").also(registriesInternal::add).also { adapters.add(it.adapter()) }
|
||||||
|
val grassVariants = Registry<GrassVariant.Data>("grass variant").also(registriesInternal::add).also { adapters.add(it.adapter()) }
|
||||||
|
val treeStemVariants = Registry<TreeVariant.StemData>("tree stem variant").also(registriesInternal::add).also { adapters.add(it.adapter()) }
|
||||||
|
val treeFoliageVariants = Registry<TreeVariant.FoliageData>("tree foliage variant").also(registriesInternal::add).also { adapters.add(it.adapter()) }
|
||||||
|
val bushVariants = Registry<BushVariant.Data>("bush variant").also(registriesInternal::add).also { adapters.add(it.adapter()) }
|
||||||
|
|
||||||
private fun <T> key(mapper: (T) -> String): (T) -> Pair<String, Int?> {
|
private fun <T> key(mapper: (T) -> String): (T) -> Pair<String, Int?> {
|
||||||
return { mapper.invoke(it) to null }
|
return { mapper.invoke(it) to null }
|
||||||
@ -69,33 +95,16 @@ object Registries {
|
|||||||
return { mapper.invoke(it) to mapperInt.invoke(it) }
|
return { mapper.invoke(it) to mapperInt.invoke(it) }
|
||||||
}
|
}
|
||||||
|
|
||||||
fun validate(): Boolean {
|
fun validate(): CompletableFuture<Boolean> {
|
||||||
var any = false
|
val futures = ArrayList<CompletableFuture<Boolean>>()
|
||||||
|
|
||||||
any = !tiles.validate() || any
|
for (registry in registriesInternal)
|
||||||
any = !tileModifiers.validate() || any
|
futures.add(CompletableFuture.supplyAsync { registry.validate() })
|
||||||
any = !liquid.validate() || any
|
|
||||||
any = !species.validate() || any
|
|
||||||
any = !statusEffects.validate() || any
|
|
||||||
any = !particles.validate() || any
|
|
||||||
any = !items.validate() || any
|
|
||||||
any = !questTemplates.validate() || any
|
|
||||||
any = !techs.validate() || any
|
|
||||||
any = !jsonFunctions.validate() || any
|
|
||||||
any = !json2Functions.validate() || any
|
|
||||||
any = !npcTypes.validate() || any
|
|
||||||
any = !projectiles.validate() || any
|
|
||||||
any = !tenants.validate() || any
|
|
||||||
any = !treasurePools.validate() || any
|
|
||||||
any = !monsterSkills.validate() || any
|
|
||||||
any = !monsterTypes.validate() || any
|
|
||||||
any = !worldObjects.validate() || any
|
|
||||||
|
|
||||||
return !any
|
return CompletableFuture.allOf(*futures.toTypedArray()).thenApply { futures.all { it.get() } }
|
||||||
}
|
}
|
||||||
|
|
||||||
private inline fun <reified T : Any> loadRegistry(
|
private inline fun <reified T : Any> loadRegistry(
|
||||||
executor: ExecutorService,
|
|
||||||
registry: Registry<T>,
|
registry: Registry<T>,
|
||||||
files: List<IStarboundFile>,
|
files: List<IStarboundFile>,
|
||||||
noinline keyProvider: (T) -> Pair<String, Int?>
|
noinline keyProvider: (T) -> Pair<String, Int?>
|
||||||
@ -104,9 +113,10 @@ object Registries {
|
|||||||
val elementAdapter by lazy { Starbound.gson.getAdapter(JsonElement::class.java) }
|
val elementAdapter by lazy { Starbound.gson.getAdapter(JsonElement::class.java) }
|
||||||
|
|
||||||
return files.map { listedFile ->
|
return files.map { listedFile ->
|
||||||
executor.submit {
|
Starbound.EXECUTOR.submit {
|
||||||
try {
|
try {
|
||||||
AssetPathStack(listedFile.computeDirectory()) {
|
AssetPathStack(listedFile.computeDirectory()) {
|
||||||
|
// TODO: json patch support
|
||||||
val elem = elementAdapter.read(listedFile.jsonReader())
|
val elem = elementAdapter.read(listedFile.jsonReader())
|
||||||
val read = adapter.fromJsonTree(elem)
|
val read = adapter.fromJsonTree(elem)
|
||||||
val keys = keyProvider(read)
|
val keys = keyProvider(read)
|
||||||
@ -126,35 +136,43 @@ object Registries {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun finishLoad() {
|
fun finishLoad() {
|
||||||
registries.forEach { it.finishLoad() }
|
registriesInternal.forEach { it.finishLoad() }
|
||||||
}
|
}
|
||||||
|
|
||||||
fun load(fileTree: Map<String, List<IStarboundFile>>, executor: ExecutorService): List<Future<*>> {
|
fun load(fileTree: Map<String, List<IStarboundFile>>): List<Future<*>> {
|
||||||
val tasks = ArrayList<Future<*>>()
|
val tasks = ArrayList<Future<*>>()
|
||||||
|
|
||||||
tasks.addAll(loadItemDefinitions(fileTree, executor))
|
tasks.addAll(loadItemDefinitions(fileTree))
|
||||||
|
|
||||||
tasks.addAll(loadJsonFunctions(fileTree["functions"] ?: listOf(), executor))
|
tasks.addAll(loadTerrainSelectors(fileTree["terrain"] ?: listOf()))
|
||||||
tasks.addAll(loadJson2Functions(fileTree["2functions"] ?: listOf(), executor))
|
|
||||||
tasks.addAll(loadTreasurePools(fileTree["treasurepools"] ?: listOf(), executor))
|
|
||||||
|
|
||||||
tasks.addAll(loadRegistry(executor, tiles, fileTree["material"] ?: listOf(), key(TileDefinition::materialName, TileDefinition::materialId)))
|
tasks.addAll(loadRegistry(tiles, fileTree["material"] ?: listOf(), key(TileDefinition::materialName, TileDefinition::materialId)))
|
||||||
tasks.addAll(loadRegistry(executor, tileModifiers, fileTree["matmod"] ?: listOf(), key(MaterialModifier::modName, MaterialModifier::modId)))
|
tasks.addAll(loadRegistry(tileModifiers, fileTree["matmod"] ?: listOf(), key(MaterialModifier::modName, MaterialModifier::modId)))
|
||||||
tasks.addAll(loadRegistry(executor, liquid, fileTree["liquid"] ?: listOf(), key(LiquidDefinition::name, LiquidDefinition::liquidId)))
|
tasks.addAll(loadRegistry(liquid, fileTree["liquid"] ?: listOf(), key(LiquidDefinition::name, LiquidDefinition::liquidId)))
|
||||||
|
|
||||||
tasks.addAll(loadRegistry(executor, worldObjects, fileTree["object"] ?: listOf(), key(ObjectDefinition::objectName)))
|
tasks.addAll(loadRegistry(worldObjects, fileTree["object"] ?: listOf(), key(ObjectDefinition::objectName)))
|
||||||
tasks.addAll(loadRegistry(executor, statusEffects, fileTree["statuseffect"] ?: listOf(), key(StatusEffectDefinition::name)))
|
tasks.addAll(loadRegistry(statusEffects, fileTree["statuseffect"] ?: listOf(), key(StatusEffectDefinition::name)))
|
||||||
tasks.addAll(loadRegistry(executor, species, fileTree["species"] ?: listOf(), key(Species::kind)))
|
tasks.addAll(loadRegistry(species, fileTree["species"] ?: listOf(), key(Species::kind)))
|
||||||
tasks.addAll(loadRegistry(executor, particles, fileTree["particle"] ?: listOf(), key(ParticleDefinition::kind)))
|
tasks.addAll(loadRegistry(particles, fileTree["particle"] ?: listOf(), key(ParticleDefinition::kind)))
|
||||||
tasks.addAll(loadRegistry(executor, questTemplates, fileTree["questtemplate"] ?: listOf(), key(QuestTemplate::id)))
|
tasks.addAll(loadRegistry(questTemplates, fileTree["questtemplate"] ?: listOf(), key(QuestTemplate::id)))
|
||||||
tasks.addAll(loadRegistry(executor, techs, fileTree["tech"] ?: listOf(), key(TechDefinition::name)))
|
tasks.addAll(loadRegistry(techs, fileTree["tech"] ?: listOf(), key(TechDefinition::name)))
|
||||||
tasks.addAll(loadRegistry(executor, npcTypes, fileTree["npctype"] ?: listOf(), key(NpcTypeDefinition::type)))
|
tasks.addAll(loadRegistry(npcTypes, fileTree["npctype"] ?: listOf(), key(NpcTypeDefinition::type)))
|
||||||
tasks.addAll(loadRegistry(executor, monsterSkills, fileTree["monsterskill"] ?: listOf(), key(MonsterSkillDefinition::name)))
|
tasks.addAll(loadRegistry(monsterSkills, fileTree["monsterskill"] ?: listOf(), key(MonsterSkillDefinition::name)))
|
||||||
|
tasks.addAll(loadRegistry(biomes, fileTree["biome"] ?: listOf(), key(BiomeDefinition::name)))
|
||||||
|
tasks.addAll(loadRegistry(grassVariants, fileTree["grass"] ?: listOf(), key(GrassVariant.Data::name)))
|
||||||
|
tasks.addAll(loadRegistry(treeStemVariants, fileTree["modularstem"] ?: listOf(), key(TreeVariant.StemData::name)))
|
||||||
|
tasks.addAll(loadRegistry(treeFoliageVariants, fileTree["modularfoliage"] ?: listOf(), key(TreeVariant.FoliageData::name)))
|
||||||
|
tasks.addAll(loadRegistry(bushVariants, fileTree["bush"] ?: listOf(), key(BushVariant.Data::name)))
|
||||||
|
|
||||||
|
tasks.addAll(loadCombined(jsonFunctions, fileTree["functions"] ?: listOf()))
|
||||||
|
tasks.addAll(loadCombined(json2Functions, fileTree["2functions"] ?: listOf()))
|
||||||
|
tasks.addAll(loadCombined(jsonConfigFunctions, fileTree["configfunctions"] ?: listOf()))
|
||||||
|
tasks.addAll(loadCombined(treasurePools, fileTree["treasurepools"] ?: listOf()) { name = it })
|
||||||
|
|
||||||
return tasks
|
return tasks
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun loadItemDefinitions(files: Map<String, Collection<IStarboundFile>>, executor: ExecutorService): List<Future<*>> {
|
private fun loadItemDefinitions(files: Map<String, Collection<IStarboundFile>>): List<Future<*>> {
|
||||||
val fileMap = mapOf(
|
val fileMap = mapOf(
|
||||||
"item" to ItemDefinition::class.java,
|
"item" to ItemDefinition::class.java,
|
||||||
"currency" to CurrencyItemDefinition::class.java,
|
"currency" to CurrencyItemDefinition::class.java,
|
||||||
@ -176,7 +194,7 @@ object Registries {
|
|||||||
val adapter by lazy { Starbound.gson.getAdapter(clazz) }
|
val adapter by lazy { Starbound.gson.getAdapter(clazz) }
|
||||||
|
|
||||||
for (listedFile in fileList) {
|
for (listedFile in fileList) {
|
||||||
tasks.add(executor.submit {
|
tasks.add(Starbound.EXECUTOR.submit {
|
||||||
try {
|
try {
|
||||||
val json = objects.read(listedFile.jsonReader())
|
val json = objects.read(listedFile.jsonReader())
|
||||||
val def = AssetPathStack(listedFile.computeDirectory()) { adapter.fromJsonTree(json) }
|
val def = AssetPathStack(listedFile.computeDirectory()) { adapter.fromJsonTree(json) }
|
||||||
@ -194,65 +212,46 @@ object Registries {
|
|||||||
return tasks
|
return tasks
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun loadJsonFunctions(files: Collection<IStarboundFile>, executor: ExecutorService): List<Future<*>> {
|
private inline fun <reified T : Any> loadCombined(registry: Registry<T>, files: Collection<IStarboundFile>, noinline transform: T.(String) -> Unit = {}): List<Future<*>> {
|
||||||
|
val adapter by lazy { Starbound.gson.getAdapter(T::class.java) }
|
||||||
|
val elementAdapter by lazy { Starbound.gson.getAdapter(JsonObject::class.java) }
|
||||||
|
|
||||||
return files.map { listedFile ->
|
return files.map { listedFile ->
|
||||||
executor.submit {
|
Starbound.EXECUTOR.submit {
|
||||||
try {
|
try {
|
||||||
val json = Starbound.gson.getAdapter(JsonObject::class.java).read(JsonReader(listedFile.reader()).also { it.isLenient = true })
|
// TODO: json patch support
|
||||||
|
val json = elementAdapter.read(JsonReader(listedFile.reader()).also { it.isLenient = true })
|
||||||
|
|
||||||
for ((k, v) in json.entrySet()) {
|
for ((k, v) in json.entrySet()) {
|
||||||
try {
|
try {
|
||||||
val fn = Starbound.gson.fromJson<JsonFunction>(JsonTreeReader(v), JsonFunction::class.java)
|
val value = adapter.fromJsonTree(v)
|
||||||
jsonFunctions.add(k, fn, v, listedFile)
|
transform(value, k)
|
||||||
|
|
||||||
|
registry.add {
|
||||||
|
registry.add(k, value, v, listedFile)
|
||||||
|
}
|
||||||
} catch (err: Exception) {
|
} catch (err: Exception) {
|
||||||
LOGGER.error("Loading json function definition $k from file $listedFile", err)
|
LOGGER.error("Loading ${registry.name} definition $k from file $listedFile", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (err: Exception) {
|
} catch (err: Exception) {
|
||||||
LOGGER.error("Loading json function definition $listedFile", err)
|
LOGGER.error("Loading ${registry.name} definition $listedFile", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun loadJson2Functions(files: Collection<IStarboundFile>, executor: ExecutorService): List<Future<*>> {
|
private fun loadTerrainSelectors(files: Collection<IStarboundFile>): List<Future<*>> {
|
||||||
return files.map { listedFile ->
|
return files.map { listedFile ->
|
||||||
executor.submit {
|
Starbound.EXECUTOR.submit {
|
||||||
try {
|
try {
|
||||||
val json = Starbound.gson.getAdapter(JsonObject::class.java).read(JsonReader(listedFile.reader()).also { it.isLenient = true })
|
val factory = TerrainSelectorType.createFactory(Starbound.gson.getAdapter(JsonObject::class.java).read(JsonReader(listedFile.reader()).also { it.isLenient = true }))
|
||||||
|
|
||||||
for ((k, v) in json.entrySet()) {
|
terrainSelectors.add {
|
||||||
try {
|
terrainSelectors.add(factory.name, factory)
|
||||||
val fn = Starbound.gson.fromJson<Json2Function>(JsonTreeReader(v), Json2Function::class.java)
|
|
||||||
json2Functions.add(k, fn, v, listedFile)
|
|
||||||
} catch (err: Throwable) {
|
|
||||||
LOGGER.error("Loading json 2function definition $k from file $listedFile", err)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} catch (err: Exception) {
|
} catch (err: Exception) {
|
||||||
LOGGER.error("Loading json 2function definition $listedFile", err)
|
LOGGER.error("Loading terrain selector $listedFile", err)
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun loadTreasurePools(files: Collection<IStarboundFile>, executor: ExecutorService): List<Future<*>> {
|
|
||||||
return files.map { listedFile ->
|
|
||||||
executor.submit {
|
|
||||||
try {
|
|
||||||
val json = Starbound.gson.getAdapter(JsonObject::class.java).read(JsonReader(listedFile.reader()).also { it.isLenient = true })
|
|
||||||
|
|
||||||
for ((k, v) in json.entrySet()) {
|
|
||||||
try {
|
|
||||||
val result = Starbound.gson.fromJson<TreasurePoolDefinition>(JsonTreeReader(v), TreasurePoolDefinition::class.java)
|
|
||||||
result.name = k
|
|
||||||
treasurePools.add(result.name, result, v, listedFile)
|
|
||||||
} catch (err: Throwable) {
|
|
||||||
LOGGER.error("Loading treasure pool definition $k from file $listedFile", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (err: Exception) {
|
|
||||||
LOGGER.error("Loading treasure pool definition $listedFile", err)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,16 +1,8 @@
|
|||||||
package ru.dbotthepony.kstarbound
|
package ru.dbotthepony.kstarbound
|
||||||
|
|
||||||
import com.google.gson.Gson
|
|
||||||
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 com.google.gson.JsonObject
|
||||||
import com.google.gson.JsonSyntaxException
|
|
||||||
import com.google.gson.TypeAdapter
|
|
||||||
import com.google.gson.TypeAdapterFactory
|
|
||||||
import com.google.gson.reflect.TypeToken
|
|
||||||
import com.google.gson.stream.JsonReader
|
|
||||||
import com.google.gson.stream.JsonToken
|
|
||||||
import com.google.gson.stream.JsonWriter
|
|
||||||
import it.unimi.dsi.fastutil.ints.Int2ObjectFunction
|
import it.unimi.dsi.fastutil.ints.Int2ObjectFunction
|
||||||
import it.unimi.dsi.fastutil.ints.Int2ObjectMap
|
import it.unimi.dsi.fastutil.ints.Int2ObjectMap
|
||||||
import it.unimi.dsi.fastutil.ints.Int2ObjectMaps
|
import it.unimi.dsi.fastutil.ints.Int2ObjectMaps
|
||||||
@ -21,76 +13,12 @@ import it.unimi.dsi.fastutil.objects.Object2ObjectMaps
|
|||||||
import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap
|
import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap
|
||||||
import org.apache.logging.log4j.LogManager
|
import org.apache.logging.log4j.LogManager
|
||||||
import ru.dbotthepony.kommons.util.Either
|
import ru.dbotthepony.kommons.util.Either
|
||||||
import ru.dbotthepony.kommons.gson.consumeNull
|
|
||||||
import java.lang.reflect.ParameterizedType
|
|
||||||
import java.util.concurrent.locks.ReentrantLock
|
|
||||||
import java.util.function.Supplier
|
|
||||||
import kotlin.collections.contains
|
|
||||||
import kotlin.collections.set
|
|
||||||
import kotlin.concurrent.withLock
|
|
||||||
import ru.dbotthepony.kstarbound.util.traverseJsonPath
|
import ru.dbotthepony.kstarbound.util.traverseJsonPath
|
||||||
import java.util.concurrent.ConcurrentLinkedQueue
|
import java.util.concurrent.ConcurrentLinkedQueue
|
||||||
|
import java.util.concurrent.locks.ReentrantLock
|
||||||
inline fun <reified S : Any> Registry<S>.adapter(): TypeAdapterFactory {
|
import java.util.function.Supplier
|
||||||
return object : TypeAdapterFactory {
|
import kotlin.collections.set
|
||||||
override fun <T : Any?> create(gson: Gson, type: TypeToken<T>): TypeAdapter<T>? {
|
import kotlin.concurrent.withLock
|
||||||
val subtype = type.type as? ParameterizedType ?: return null
|
|
||||||
if (subtype.actualTypeArguments.size != 1 || subtype.actualTypeArguments[0] != S::class.java) return null
|
|
||||||
|
|
||||||
return when (type.rawType) {
|
|
||||||
Registry.Entry::class.java -> {
|
|
||||||
object : TypeAdapter<Registry.Entry<S>>() {
|
|
||||||
override fun write(out: JsonWriter, value: Registry.Entry<S>?) {
|
|
||||||
if (value != null) {
|
|
||||||
out.value(value.key)
|
|
||||||
} else {
|
|
||||||
out.nullValue()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun read(`in`: JsonReader): Registry.Entry<S>? {
|
|
||||||
if (`in`.consumeNull()) {
|
|
||||||
return null
|
|
||||||
} else if (`in`.peek() == JsonToken.STRING) {
|
|
||||||
return this@adapter[`in`.nextString()]
|
|
||||||
} else if (`in`.peek() == JsonToken.NUMBER) {
|
|
||||||
return this@adapter[`in`.nextInt()]
|
|
||||||
} else {
|
|
||||||
throw JsonSyntaxException("Expected registry key or registry id, got ${`in`.peek()}")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Registry.Ref::class.java -> {
|
|
||||||
object : TypeAdapter<Registry.Ref<S>>() {
|
|
||||||
override fun write(out: JsonWriter, value: Registry.Ref<S>?) {
|
|
||||||
if (value != null) {
|
|
||||||
value.key.map(out::value, out::value)
|
|
||||||
} else {
|
|
||||||
out.nullValue()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun read(`in`: JsonReader): Registry.Ref<S>? {
|
|
||||||
if (`in`.consumeNull()) {
|
|
||||||
return null
|
|
||||||
} else if (`in`.peek() == JsonToken.STRING) {
|
|
||||||
return this@adapter.ref(`in`.nextString())
|
|
||||||
} else if (`in`.peek() == JsonToken.NUMBER) {
|
|
||||||
return this@adapter.ref(`in`.nextInt())
|
|
||||||
} else {
|
|
||||||
throw JsonSyntaxException("Expected registry key or registry id, got ${`in`.peek()}")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
else -> null
|
|
||||||
} as TypeAdapter<T>?
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class Registry<T : Any>(val name: String) {
|
class Registry<T : Any>(val name: String) {
|
||||||
private val keysInternal = Object2ObjectOpenHashMap<String, Impl>()
|
private val keysInternal = Object2ObjectOpenHashMap<String, Impl>()
|
||||||
@ -105,11 +33,13 @@ class Registry<T : Any>(val name: String) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun finishLoad() {
|
fun finishLoad() {
|
||||||
var next = backlog.poll()
|
lock.withLock {
|
||||||
|
var next = backlog.poll()
|
||||||
|
|
||||||
while (next != null) {
|
while (next != null) {
|
||||||
next.run()
|
next.run()
|
||||||
next = backlog.poll()
|
next = backlog.poll()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -193,7 +123,7 @@ class Registry<T : Any>(val name: String) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun toString(): String {
|
override fun toString(): String {
|
||||||
return "Registry.Ref[key=$key, bound=${entry != null}, registry=$name]"
|
return "Registry.Ref[key=$key, bound to value=${entry != null}, registry=$name]"
|
||||||
}
|
}
|
||||||
|
|
||||||
override val registry: Registry<T>
|
override val registry: Registry<T>
|
||||||
@ -203,6 +133,14 @@ class Registry<T : Any>(val name: String) {
|
|||||||
operator fun get(index: String): Entry<T>? = lock.withLock { keysInternal[index] }
|
operator fun get(index: String): Entry<T>? = lock.withLock { keysInternal[index] }
|
||||||
operator fun get(index: Int): Entry<T>? = lock.withLock { idsInternal[index] }
|
operator fun get(index: Int): Entry<T>? = lock.withLock { idsInternal[index] }
|
||||||
|
|
||||||
|
fun getOrThrow(index: String): Entry<T> {
|
||||||
|
return get(index) ?: throw NoSuchElementException("No such $name: $index")
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getOrThrow(index: Int): Entry<T> {
|
||||||
|
return get(index) ?: throw NoSuchElementException("No such $name: $index")
|
||||||
|
}
|
||||||
|
|
||||||
fun ref(index: String): Ref<T> = lock.withLock {
|
fun ref(index: String): Ref<T> = lock.withLock {
|
||||||
keyRefs.computeIfAbsent(index, Object2ObjectFunction {
|
keyRefs.computeIfAbsent(index, Object2ObjectFunction {
|
||||||
val ref = RefImpl(Either.left(it as String))
|
val ref = RefImpl(Either.left(it as String))
|
||||||
@ -227,29 +165,29 @@ class Registry<T : Any>(val name: String) {
|
|||||||
operator fun contains(index: Int) = lock.withLock { index in idsInternal }
|
operator fun contains(index: Int) = lock.withLock { index in idsInternal }
|
||||||
|
|
||||||
fun validate(): Boolean {
|
fun validate(): Boolean {
|
||||||
var any = true
|
var valid = true
|
||||||
|
|
||||||
keyRefs.values.forEach {
|
keyRefs.values.forEach {
|
||||||
if (!it.isPresent) {
|
if (!it.isPresent) {
|
||||||
LOGGER.warn("Registry '$name' reference at '${it.key.left()}' is not bound to value, expect problems (referenced ${it.references} times)")
|
LOGGER.warn("Registry '$name' reference at '${it.key.left()}' is not bound to value, expect problems (referenced ${it.references} times)")
|
||||||
any = false
|
valid = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
idRefs.values.forEach {
|
idRefs.values.forEach {
|
||||||
if (!it.isPresent) {
|
if (!it.isPresent) {
|
||||||
LOGGER.warn("Registry '$name' reference with ID '${it.key.right()}' is not bound to value, expect problems (referenced ${it.references} times)")
|
LOGGER.warn("Registry '$name' reference with ID '${it.key.right()}' is not bound to value, expect problems (referenced ${it.references} times)")
|
||||||
any = false
|
valid = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return any
|
return valid
|
||||||
}
|
}
|
||||||
|
|
||||||
fun add(key: String, value: T, json: JsonElement, file: IStarboundFile): Entry<T> {
|
fun add(key: String, value: T, json: JsonElement, file: IStarboundFile): Entry<T> {
|
||||||
lock.withLock {
|
lock.withLock {
|
||||||
if (key in keysInternal) {
|
if (key in keysInternal) {
|
||||||
LOGGER.warn("Overwriting Registry entry at '$key' (new def originate from $file; old def originate from ${keysInternal[key]?.file ?: "<code>"})")
|
LOGGER.warn("Overwriting $name at '$key' (new def originate from $file; old def originate from ${keysInternal[key]?.file ?: "<code>"})")
|
||||||
}
|
}
|
||||||
|
|
||||||
val entry = keysInternal.computeIfAbsent(key, Object2ObjectFunction { Impl(key, value) })
|
val entry = keysInternal.computeIfAbsent(key, Object2ObjectFunction { Impl(key, value) })
|
||||||
@ -275,11 +213,11 @@ class Registry<T : Any>(val name: String) {
|
|||||||
fun add(key: String, id: Int, value: T, json: JsonElement, file: IStarboundFile): Entry<T> {
|
fun add(key: String, id: Int, value: T, json: JsonElement, file: IStarboundFile): Entry<T> {
|
||||||
lock.withLock {
|
lock.withLock {
|
||||||
if (key in keysInternal) {
|
if (key in keysInternal) {
|
||||||
LOGGER.warn("Overwriting Registry entry at '$key' (new def originate from $file; old def originate from ${keysInternal[key]?.file ?: "<code>"})")
|
LOGGER.warn("Overwriting $name at '$key' (new def originate from $file; old def originate from ${keysInternal[key]?.file ?: "<code>"})")
|
||||||
}
|
}
|
||||||
|
|
||||||
if (id in idsInternal) {
|
if (id in idsInternal) {
|
||||||
LOGGER.warn("Overwriting Registry entry with ID '$id' (new def originate from $file; old def originate from ${idsInternal[id]?.file ?: "<code>"})")
|
LOGGER.warn("Overwriting $name with ID '$id' (new def originate from $file; old def originate from ${idsInternal[id]?.file ?: "<code>"})")
|
||||||
}
|
}
|
||||||
|
|
||||||
val entry = keysInternal.computeIfAbsent(key, Object2ObjectFunction { Impl(key, value) })
|
val entry = keysInternal.computeIfAbsent(key, Object2ObjectFunction { Impl(key, value) })
|
||||||
@ -307,7 +245,7 @@ class Registry<T : Any>(val name: String) {
|
|||||||
fun add(key: String, value: T, isBuiltin: Boolean = false): Entry<T> {
|
fun add(key: String, value: T, isBuiltin: Boolean = false): Entry<T> {
|
||||||
lock.withLock {
|
lock.withLock {
|
||||||
if (key in keysInternal) {
|
if (key in keysInternal) {
|
||||||
LOGGER.warn("Overwriting Registry entry at '$key' (new def originate from <code>; old def originate from ${keysInternal[key]?.file ?: "<code>"})")
|
LOGGER.warn("Overwriting $name at '$key' (new def originate from <code>; old def originate from ${keysInternal[key]?.file ?: "<code>"})")
|
||||||
}
|
}
|
||||||
|
|
||||||
val entry = keysInternal.computeIfAbsent(key, Object2ObjectFunction { Impl(key, value) })
|
val entry = keysInternal.computeIfAbsent(key, Object2ObjectFunction { Impl(key, value) })
|
||||||
@ -334,11 +272,11 @@ class Registry<T : Any>(val name: String) {
|
|||||||
fun add(key: String, id: Int, value: T, isBuiltin: Boolean = false): Entry<T> {
|
fun add(key: String, id: Int, value: T, isBuiltin: Boolean = false): Entry<T> {
|
||||||
lock.withLock {
|
lock.withLock {
|
||||||
if (key in keysInternal) {
|
if (key in keysInternal) {
|
||||||
LOGGER.warn("Overwriting Registry entry at '$key' (new def originate from <code>; old def originate from ${keysInternal[key]?.file ?: "<code>"})")
|
LOGGER.warn("Overwriting $name at '$key' (new def originate from <code>; old def originate from ${keysInternal[key]?.file ?: "<code>"})")
|
||||||
}
|
}
|
||||||
|
|
||||||
if (id in idsInternal) {
|
if (id in idsInternal) {
|
||||||
LOGGER.warn("Overwriting Registry entry with ID '$id' (new def originate from <code>; old def originate from ${idsInternal[id]?.file ?: "<code>"})")
|
LOGGER.warn("Overwriting $name with ID '$id' (new def originate from <code>; old def originate from ${idsInternal[id]?.file ?: "<code>"})")
|
||||||
}
|
}
|
||||||
|
|
||||||
val entry = keysInternal.computeIfAbsent(key, Object2ObjectFunction { Impl(key, value) })
|
val entry = keysInternal.computeIfAbsent(key, Object2ObjectFunction { Impl(key, value) })
|
||||||
|
@ -0,0 +1,76 @@
|
|||||||
|
package ru.dbotthepony.kstarbound
|
||||||
|
|
||||||
|
import com.google.gson.Gson
|
||||||
|
import com.google.gson.JsonSyntaxException
|
||||||
|
import com.google.gson.TypeAdapter
|
||||||
|
import com.google.gson.TypeAdapterFactory
|
||||||
|
import com.google.gson.reflect.TypeToken
|
||||||
|
import com.google.gson.stream.JsonReader
|
||||||
|
import com.google.gson.stream.JsonToken
|
||||||
|
import com.google.gson.stream.JsonWriter
|
||||||
|
import ru.dbotthepony.kommons.gson.consumeNull
|
||||||
|
import java.lang.reflect.ParameterizedType
|
||||||
|
import kotlin.reflect.KClass
|
||||||
|
|
||||||
|
inline fun <reified S : Any> Registry<S>.adapter(): TypeAdapterFactory {
|
||||||
|
return RegistryTypeAdapterFactory(this, S::class)
|
||||||
|
}
|
||||||
|
|
||||||
|
class RegistryTypeAdapterFactory<S : Any>(private val registry: Registry<S>, private val clazz: KClass<S>) : TypeAdapterFactory {
|
||||||
|
override fun <T : Any> create(gson: Gson, type: TypeToken<T>): TypeAdapter<T>? {
|
||||||
|
val subtype = type.type as? ParameterizedType ?: return null
|
||||||
|
if (subtype.actualTypeArguments.size != 1 || subtype.actualTypeArguments[0] != clazz.java) return null
|
||||||
|
|
||||||
|
if (type.rawType == Registry.Entry::class.java) {
|
||||||
|
return EntryImpl(gson) as TypeAdapter<T>
|
||||||
|
} else if (type.rawType == Registry.Ref::class.java) {
|
||||||
|
return RefImpl(gson) as TypeAdapter<T>
|
||||||
|
}
|
||||||
|
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
private inner class EntryImpl(gson: Gson) : TypeAdapter<Registry.Entry<S>>() {
|
||||||
|
override fun write(out: JsonWriter, value: Registry.Entry<S>?) {
|
||||||
|
if (value != null) {
|
||||||
|
out.value(value.key)
|
||||||
|
} else {
|
||||||
|
out.nullValue()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun read(`in`: JsonReader): Registry.Entry<S>? {
|
||||||
|
if (`in`.consumeNull()) {
|
||||||
|
return null
|
||||||
|
} else if (`in`.peek() == JsonToken.STRING) {
|
||||||
|
return registry[`in`.nextString()]
|
||||||
|
} else if (`in`.peek() == JsonToken.NUMBER) {
|
||||||
|
return registry[`in`.nextInt()]
|
||||||
|
} else {
|
||||||
|
throw JsonSyntaxException("Expected registry key or registry id, got ${`in`.peek()}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private inner class RefImpl(gson: Gson) : TypeAdapter<Registry.Ref<S>>() {
|
||||||
|
override fun write(out: JsonWriter, value: Registry.Ref<S>?) {
|
||||||
|
if (value != null) {
|
||||||
|
value.key.map(out::value, out::value)
|
||||||
|
} else {
|
||||||
|
out.nullValue()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun read(`in`: JsonReader): Registry.Ref<S>? {
|
||||||
|
if (`in`.consumeNull()) {
|
||||||
|
return null
|
||||||
|
} else if (`in`.peek() == JsonToken.STRING) {
|
||||||
|
return registry.ref(`in`.nextString())
|
||||||
|
} else if (`in`.peek() == JsonToken.NUMBER) {
|
||||||
|
return registry.ref(`in`.nextInt())
|
||||||
|
} else {
|
||||||
|
throw JsonSyntaxException("Expected registry key or registry id, got ${`in`.peek()}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -4,10 +4,11 @@ import com.github.benmanes.caffeine.cache.Interner
|
|||||||
import com.google.gson.*
|
import com.google.gson.*
|
||||||
import it.unimi.dsi.fastutil.objects.Object2ObjectFunction
|
import it.unimi.dsi.fastutil.objects.Object2ObjectFunction
|
||||||
import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap
|
import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap
|
||||||
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
import kotlinx.coroutines.asCoroutineDispatcher
|
||||||
import org.apache.logging.log4j.LogManager
|
import org.apache.logging.log4j.LogManager
|
||||||
import ru.dbotthepony.kommons.gson.AABBTypeAdapter
|
import ru.dbotthepony.kommons.gson.AABBTypeAdapter
|
||||||
import ru.dbotthepony.kommons.gson.AABBiTypeAdapter
|
import ru.dbotthepony.kommons.gson.AABBiTypeAdapter
|
||||||
import ru.dbotthepony.kommons.gson.ColorTypeAdapter
|
|
||||||
import ru.dbotthepony.kommons.gson.EitherTypeAdapter
|
import ru.dbotthepony.kommons.gson.EitherTypeAdapter
|
||||||
import ru.dbotthepony.kommons.gson.KOptionalTypeAdapter
|
import ru.dbotthepony.kommons.gson.KOptionalTypeAdapter
|
||||||
import ru.dbotthepony.kommons.gson.NothingAdapter
|
import ru.dbotthepony.kommons.gson.NothingAdapter
|
||||||
@ -27,8 +28,14 @@ import ru.dbotthepony.kstarbound.defs.item.TreasurePoolDefinition
|
|||||||
import ru.dbotthepony.kstarbound.defs.`object`.ObjectDefinition
|
import ru.dbotthepony.kstarbound.defs.`object`.ObjectDefinition
|
||||||
import ru.dbotthepony.kstarbound.defs.`object`.ObjectOrientation
|
import ru.dbotthepony.kstarbound.defs.`object`.ObjectOrientation
|
||||||
import ru.dbotthepony.kstarbound.defs.player.BlueprintLearnList
|
import ru.dbotthepony.kstarbound.defs.player.BlueprintLearnList
|
||||||
|
import ru.dbotthepony.kstarbound.defs.world.CelestialParameters
|
||||||
|
import ru.dbotthepony.kstarbound.defs.world.VisitableWorldParametersType
|
||||||
|
import ru.dbotthepony.kstarbound.defs.world.terrain.BiomePlaceables
|
||||||
|
import ru.dbotthepony.kstarbound.defs.world.terrain.BiomePlacementDistributionType
|
||||||
|
import ru.dbotthepony.kstarbound.defs.world.terrain.BiomePlacementItemType
|
||||||
|
import ru.dbotthepony.kstarbound.defs.world.terrain.TerrainSelectorType
|
||||||
import ru.dbotthepony.kstarbound.io.*
|
import ru.dbotthepony.kstarbound.io.*
|
||||||
import ru.dbotthepony.kstarbound.json.FastutilTypeAdapterFactory
|
import ru.dbotthepony.kstarbound.json.factory.MapsTypeAdapterFactory
|
||||||
import ru.dbotthepony.kstarbound.json.InternedJsonElementAdapter
|
import ru.dbotthepony.kstarbound.json.InternedJsonElementAdapter
|
||||||
import ru.dbotthepony.kstarbound.json.InternedStringAdapter
|
import ru.dbotthepony.kstarbound.json.InternedStringAdapter
|
||||||
import ru.dbotthepony.kstarbound.json.LongRangeAdapter
|
import ru.dbotthepony.kstarbound.json.LongRangeAdapter
|
||||||
@ -36,9 +43,11 @@ import ru.dbotthepony.kstarbound.json.builder.EnumAdapter
|
|||||||
import ru.dbotthepony.kstarbound.json.builder.BuilderAdapter
|
import ru.dbotthepony.kstarbound.json.builder.BuilderAdapter
|
||||||
import ru.dbotthepony.kstarbound.json.builder.FactoryAdapter
|
import ru.dbotthepony.kstarbound.json.builder.FactoryAdapter
|
||||||
import ru.dbotthepony.kstarbound.json.builder.JsonImplementationTypeFactory
|
import ru.dbotthepony.kstarbound.json.builder.JsonImplementationTypeFactory
|
||||||
import ru.dbotthepony.kstarbound.json.factory.ArrayListAdapterFactory
|
import ru.dbotthepony.kstarbound.json.factory.CollectionAdapterFactory
|
||||||
import ru.dbotthepony.kstarbound.json.factory.ImmutableCollectionAdapterFactory
|
import ru.dbotthepony.kstarbound.json.factory.ImmutableCollectionAdapterFactory
|
||||||
import ru.dbotthepony.kstarbound.json.factory.PairAdapterFactory
|
import ru.dbotthepony.kstarbound.json.factory.PairAdapterFactory
|
||||||
|
import ru.dbotthepony.kstarbound.json.factory.RGBAColorTypeAdapter
|
||||||
|
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.util.ItemStack
|
import ru.dbotthepony.kstarbound.util.ItemStack
|
||||||
@ -52,10 +61,10 @@ import java.io.*
|
|||||||
import java.lang.ref.Cleaner
|
import java.lang.ref.Cleaner
|
||||||
import java.text.DateFormat
|
import java.text.DateFormat
|
||||||
import java.util.concurrent.CompletableFuture
|
import java.util.concurrent.CompletableFuture
|
||||||
import java.util.concurrent.Executors
|
import java.util.concurrent.ExecutorService
|
||||||
import java.util.concurrent.ForkJoinPool
|
import java.util.concurrent.ForkJoinPool
|
||||||
import java.util.concurrent.Future
|
import java.util.concurrent.Future
|
||||||
import java.util.concurrent.SynchronousQueue
|
import java.util.concurrent.LinkedBlockingQueue
|
||||||
import java.util.concurrent.ThreadFactory
|
import java.util.concurrent.ThreadFactory
|
||||||
import java.util.concurrent.ThreadPoolExecutor
|
import java.util.concurrent.ThreadPoolExecutor
|
||||||
import java.util.concurrent.TimeUnit
|
import java.util.concurrent.TimeUnit
|
||||||
@ -100,13 +109,22 @@ object Starbound : ISBFileLocator {
|
|||||||
|
|
||||||
private val ioPoolCounter = AtomicInteger()
|
private val ioPoolCounter = AtomicInteger()
|
||||||
|
|
||||||
val STORAGE_IO_POOL = ThreadPoolExecutor(0, Int.MAX_VALUE, 30L, TimeUnit.SECONDS, SynchronousQueue(), ThreadFactory {
|
@JvmField
|
||||||
|
val IO_EXECUTOR: ExecutorService = ThreadPoolExecutor(0, 64, 30L, TimeUnit.SECONDS, LinkedBlockingQueue(), ThreadFactory {
|
||||||
val thread = Thread(it, "Starbound Storage IO ${ioPoolCounter.getAndIncrement()}")
|
val thread = Thread(it, "Starbound Storage IO ${ioPoolCounter.getAndIncrement()}")
|
||||||
thread.isDaemon = true
|
thread.isDaemon = true
|
||||||
thread.priority = Thread.MIN_PRIORITY
|
thread.priority = Thread.MIN_PRIORITY
|
||||||
return@ThreadFactory thread
|
return@ThreadFactory thread
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@JvmField
|
||||||
|
val EXECUTOR: ExecutorService = ForkJoinPool.commonPool()
|
||||||
|
@JvmField
|
||||||
|
val COROUTINE_EXECUTOR = EXECUTOR.asCoroutineDispatcher()
|
||||||
|
@JvmField
|
||||||
|
val COROUTINES = CoroutineScope(COROUTINE_EXECUTOR)
|
||||||
|
|
||||||
|
@JvmField
|
||||||
val CLEANER: Cleaner = Cleaner.create {
|
val CLEANER: Cleaner = Cleaner.create {
|
||||||
val t = Thread(it, "Starbound Global Cleaner")
|
val t = Thread(it, "Starbound Global Cleaner")
|
||||||
t.isDaemon = true
|
t.isDaemon = true
|
||||||
@ -118,6 +136,7 @@ object Starbound : ISBFileLocator {
|
|||||||
// Hrm.
|
// Hrm.
|
||||||
// val strings: Interner<String> = Interner.newWeakInterner()
|
// val strings: Interner<String> = Interner.newWeakInterner()
|
||||||
// val strings: Interner<String> = Interner { it }
|
// val strings: Interner<String> = Interner { it }
|
||||||
|
@JvmField
|
||||||
val STRINGS: Interner<String> = interner(5)
|
val STRINGS: Interner<String> = interner(5)
|
||||||
|
|
||||||
private val LOGGER = LogManager.getLogger()
|
private val LOGGER = LogManager.getLogger()
|
||||||
@ -141,14 +160,14 @@ object Starbound : ISBFileLocator {
|
|||||||
// Обработчик @JsonImplementation
|
// Обработчик @JsonImplementation
|
||||||
registerTypeAdapterFactory(JsonImplementationTypeFactory)
|
registerTypeAdapterFactory(JsonImplementationTypeFactory)
|
||||||
|
|
||||||
|
// списки, наборы, т.п.
|
||||||
|
registerTypeAdapterFactory(CollectionAdapterFactory)
|
||||||
|
|
||||||
// ImmutableList, ImmutableSet, ImmutableMap
|
// ImmutableList, ImmutableSet, ImmutableMap
|
||||||
registerTypeAdapterFactory(ImmutableCollectionAdapterFactory(STRINGS))
|
registerTypeAdapterFactory(ImmutableCollectionAdapterFactory(STRINGS))
|
||||||
|
|
||||||
// fastutil collections
|
// fastutil collections
|
||||||
registerTypeAdapterFactory(FastutilTypeAdapterFactory(STRINGS))
|
registerTypeAdapterFactory(MapsTypeAdapterFactory(STRINGS))
|
||||||
|
|
||||||
// ArrayList
|
|
||||||
registerTypeAdapterFactory(ArrayListAdapterFactory)
|
|
||||||
|
|
||||||
// все enum'ы без особых настроек
|
// все enum'ы без особых настроек
|
||||||
registerTypeAdapterFactory(EnumAdapter.Companion)
|
registerTypeAdapterFactory(EnumAdapter.Companion)
|
||||||
@ -164,6 +183,8 @@ object Starbound : ISBFileLocator {
|
|||||||
// KOptional<>
|
// KOptional<>
|
||||||
registerTypeAdapterFactory(KOptionalTypeAdapter)
|
registerTypeAdapterFactory(KOptionalTypeAdapter)
|
||||||
|
|
||||||
|
registerTypeAdapterFactory(SingletonTypeAdapterFactory)
|
||||||
|
|
||||||
// Pair<>
|
// Pair<>
|
||||||
registerTypeAdapterFactory(PairAdapterFactory)
|
registerTypeAdapterFactory(PairAdapterFactory)
|
||||||
registerTypeAdapterFactory(SBPattern.Companion)
|
registerTypeAdapterFactory(SBPattern.Companion)
|
||||||
@ -173,7 +194,7 @@ object Starbound : ISBFileLocator {
|
|||||||
registerTypeAdapter(ColorReplacements.Companion)
|
registerTypeAdapter(ColorReplacements.Companion)
|
||||||
registerTypeAdapterFactory(BlueprintLearnList.Companion)
|
registerTypeAdapterFactory(BlueprintLearnList.Companion)
|
||||||
|
|
||||||
registerTypeAdapter(ColorTypeAdapter.nullSafe())
|
registerTypeAdapter(RGBAColorTypeAdapter)
|
||||||
|
|
||||||
registerTypeAdapter(Drawable::Adapter)
|
registerTypeAdapter(Drawable::Adapter)
|
||||||
registerTypeAdapter(ObjectOrientation::Adapter)
|
registerTypeAdapter(ObjectOrientation::Adapter)
|
||||||
@ -197,10 +218,12 @@ object Starbound : ISBFileLocator {
|
|||||||
registerTypeAdapter(JsonFunction.CONSTRAINT_ADAPTER)
|
registerTypeAdapter(JsonFunction.CONSTRAINT_ADAPTER)
|
||||||
registerTypeAdapter(JsonFunction.INTERPOLATION_ADAPTER)
|
registerTypeAdapter(JsonFunction.INTERPOLATION_ADAPTER)
|
||||||
registerTypeAdapter(JsonFunction.Companion)
|
registerTypeAdapter(JsonFunction.Companion)
|
||||||
|
registerTypeAdapter(JsonConfigFunction::Adapter)
|
||||||
registerTypeAdapterFactory(Json2Function.Companion)
|
registerTypeAdapterFactory(Json2Function.Companion)
|
||||||
|
|
||||||
// Общее
|
// Общее
|
||||||
registerTypeAdapterFactory(ThingDescription.Factory(STRINGS))
|
registerTypeAdapterFactory(ThingDescription.Factory(STRINGS))
|
||||||
|
registerTypeAdapterFactory(TerrainSelectorType.Companion)
|
||||||
|
|
||||||
registerTypeAdapter(EnumAdapter(DamageType::class, default = DamageType.NORMAL))
|
registerTypeAdapter(EnumAdapter(DamageType::class, default = DamageType.NORMAL))
|
||||||
|
|
||||||
@ -223,24 +246,19 @@ object Starbound : ISBFileLocator {
|
|||||||
|
|
||||||
registerTypeAdapterFactory(Poly.Companion)
|
registerTypeAdapterFactory(Poly.Companion)
|
||||||
|
|
||||||
registerTypeAdapterFactory(Registries.tiles.adapter())
|
registerTypeAdapter(CelestialParameters::Adapter)
|
||||||
registerTypeAdapterFactory(Registries.tileModifiers.adapter())
|
|
||||||
registerTypeAdapterFactory(Registries.liquid.adapter())
|
registerTypeAdapterFactory(BiomePlacementDistributionType.DATA_ADAPTER)
|
||||||
registerTypeAdapterFactory(Registries.items.adapter())
|
registerTypeAdapterFactory(BiomePlacementDistributionType.DEFINITION_ADAPTER)
|
||||||
registerTypeAdapterFactory(Registries.species.adapter())
|
registerTypeAdapterFactory(BiomePlacementItemType.DATA_ADAPTER)
|
||||||
registerTypeAdapterFactory(Registries.statusEffects.adapter())
|
registerTypeAdapterFactory(BiomePlacementItemType.DEFINITION_ADAPTER)
|
||||||
registerTypeAdapterFactory(Registries.particles.adapter())
|
registerTypeAdapterFactory(BiomePlaceables.Item.Companion)
|
||||||
registerTypeAdapterFactory(Registries.questTemplates.adapter())
|
|
||||||
registerTypeAdapterFactory(Registries.techs.adapter())
|
// register companion first, so it has lesser priority than dispatching adapter
|
||||||
registerTypeAdapterFactory(Registries.jsonFunctions.adapter())
|
registerTypeAdapterFactory(VisitableWorldParametersType.Companion)
|
||||||
registerTypeAdapterFactory(Registries.json2Functions.adapter())
|
registerTypeAdapterFactory(VisitableWorldParametersType.ADAPTER)
|
||||||
registerTypeAdapterFactory(Registries.npcTypes.adapter())
|
|
||||||
registerTypeAdapterFactory(Registries.projectiles.adapter())
|
Registries.registerAdapters(this)
|
||||||
registerTypeAdapterFactory(Registries.tenants.adapter())
|
|
||||||
registerTypeAdapterFactory(Registries.treasurePools.adapter())
|
|
||||||
registerTypeAdapterFactory(Registries.monsterSkills.adapter())
|
|
||||||
registerTypeAdapterFactory(Registries.monsterTypes.adapter())
|
|
||||||
registerTypeAdapterFactory(Registries.worldObjects.adapter())
|
|
||||||
|
|
||||||
registerTypeAdapter(LongRangeAdapter)
|
registerTypeAdapter(LongRangeAdapter)
|
||||||
|
|
||||||
@ -418,7 +436,7 @@ object Starbound : ISBFileLocator {
|
|||||||
checkMailbox()
|
checkMailbox()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun doInitialize(parallel: Boolean) {
|
private fun doInitialize() {
|
||||||
if (!initializing && !initialized) {
|
if (!initializing && !initialized) {
|
||||||
initializing = true
|
initializing = true
|
||||||
} else {
|
} else {
|
||||||
@ -464,11 +482,10 @@ object Starbound : ISBFileLocator {
|
|||||||
checkMailbox()
|
checkMailbox()
|
||||||
|
|
||||||
val tasks = ArrayList<Future<*>>()
|
val tasks = ArrayList<Future<*>>()
|
||||||
val pool = if (parallel) ForkJoinPool.commonPool() else Executors.newFixedThreadPool(1)
|
|
||||||
|
|
||||||
tasks.addAll(Registries.load(ext2files, pool))
|
tasks.addAll(Registries.load(ext2files))
|
||||||
tasks.addAll(RecipeRegistry.load(ext2files, pool))
|
tasks.addAll(RecipeRegistry.load(ext2files))
|
||||||
tasks.addAll(GlobalDefaults.load(pool))
|
tasks.addAll(GlobalDefaults.load())
|
||||||
|
|
||||||
val total = tasks.size.toDouble()
|
val total = tasks.size.toDouble()
|
||||||
|
|
||||||
@ -479,9 +496,6 @@ object Starbound : ISBFileLocator {
|
|||||||
LockSupport.parkNanos(5_000_000L)
|
LockSupport.parkNanos(5_000_000L)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!parallel)
|
|
||||||
pool.shutdown()
|
|
||||||
|
|
||||||
Registries.finishLoad()
|
Registries.finishLoad()
|
||||||
RecipeRegistry.finishLoad()
|
RecipeRegistry.finishLoad()
|
||||||
|
|
||||||
@ -491,12 +505,12 @@ object Starbound : ISBFileLocator {
|
|||||||
initialized = true
|
initialized = true
|
||||||
}
|
}
|
||||||
|
|
||||||
fun initializeGame(parallel: Boolean = true) {
|
fun initializeGame(): Future<*> {
|
||||||
mailbox.submit { doInitialize(parallel) }
|
return mailbox.submit { doInitialize() }
|
||||||
}
|
}
|
||||||
|
|
||||||
fun bootstrapGame() {
|
fun bootstrapGame(): Future<*> {
|
||||||
mailbox.submit { doBootstrap() }
|
return mailbox.submit { doBootstrap() }
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun checkMailbox() {
|
private fun checkMailbox() {
|
||||||
|
@ -44,6 +44,7 @@ fun interface ISBFileLocator {
|
|||||||
* @throws IllegalStateException if file is a directory
|
* @throws IllegalStateException if file is a directory
|
||||||
* @throws FileNotFoundException if file does not exist
|
* @throws FileNotFoundException if file does not exist
|
||||||
*/
|
*/
|
||||||
|
@Deprecated("This does not reflect json patches")
|
||||||
fun jsonReader(path: String) = locate(path).jsonReader()
|
fun jsonReader(path: String) = locate(path).jsonReader()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -157,6 +158,7 @@ interface IStarboundFile : ISBFileLocator {
|
|||||||
* @throws IllegalStateException if file is a directory
|
* @throws IllegalStateException if file is a directory
|
||||||
* @throws FileNotFoundException if file does not exist
|
* @throws FileNotFoundException if file does not exist
|
||||||
*/
|
*/
|
||||||
|
@Deprecated("This does not reflect json patches")
|
||||||
fun jsonReader(): JsonReader = JsonReader(reader()).also { it.isLenient = true }
|
fun jsonReader(): JsonReader = JsonReader(reader()).also { it.isLenient = true }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -8,28 +8,33 @@ import io.netty.channel.local.LocalAddress
|
|||||||
import io.netty.channel.local.LocalChannel
|
import io.netty.channel.local.LocalChannel
|
||||||
import io.netty.channel.socket.nio.NioSocketChannel
|
import io.netty.channel.socket.nio.NioSocketChannel
|
||||||
import org.apache.logging.log4j.LogManager
|
import org.apache.logging.log4j.LogManager
|
||||||
|
import ru.dbotthepony.kommons.util.KOptional
|
||||||
import ru.dbotthepony.kstarbound.Starbound
|
import ru.dbotthepony.kstarbound.Starbound
|
||||||
import ru.dbotthepony.kstarbound.network.Connection
|
import ru.dbotthepony.kstarbound.network.Connection
|
||||||
import ru.dbotthepony.kstarbound.network.ConnectionSide
|
import ru.dbotthepony.kstarbound.network.ConnectionSide
|
||||||
import ru.dbotthepony.kstarbound.network.ConnectionType
|
import ru.dbotthepony.kstarbound.network.ConnectionType
|
||||||
import ru.dbotthepony.kstarbound.network.IClientPacket
|
import ru.dbotthepony.kstarbound.network.IClientPacket
|
||||||
|
import ru.dbotthepony.kstarbound.network.packets.ClientContextUpdatePacket
|
||||||
import ru.dbotthepony.kstarbound.network.packets.ProtocolRequestPacket
|
import ru.dbotthepony.kstarbound.network.packets.ProtocolRequestPacket
|
||||||
import java.net.SocketAddress
|
import java.net.SocketAddress
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
|
||||||
// client -> server
|
// clientside part of connection
|
||||||
class ClientConnection(val client: StarboundClient, type: ConnectionType, uuid: UUID) : Connection(ConnectionSide.CLIENT, type, uuid) {
|
class ClientConnection(val client: StarboundClient, type: ConnectionType) : Connection(ConnectionSide.CLIENT, type) {
|
||||||
private fun sendHello() {
|
private fun sendHello() {
|
||||||
isLegacy = false
|
isLegacy = false
|
||||||
//sendAndFlush(ProtocolRequestPacket(Starbound.LEGACY_PROTOCOL_VERSION))
|
//sendAndFlush(ProtocolRequestPacket(Starbound.LEGACY_PROTOCOL_VERSION))
|
||||||
sendAndFlush(ProtocolRequestPacket(Starbound.NATIVE_PROTOCOL_VERSION))
|
sendAndFlush(ProtocolRequestPacket(Starbound.NATIVE_PROTOCOL_VERSION))
|
||||||
}
|
}
|
||||||
|
|
||||||
var connectionID: Int = -1
|
|
||||||
|
|
||||||
override fun inGame() {
|
override fun inGame() {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun toString(): String {
|
||||||
|
val channel = if (hasChannel) channel.remoteAddress().toString() else "<no channel>"
|
||||||
|
return "ClientConnection[ID=$connectionID channel=$channel]"
|
||||||
|
}
|
||||||
|
|
||||||
override fun channelRead(ctx: ChannelHandlerContext, msg: Any) {
|
override fun channelRead(ctx: ChannelHandlerContext, msg: Any) {
|
||||||
if (msg is IClientPacket) {
|
if (msg is IClientPacket) {
|
||||||
try {
|
try {
|
||||||
@ -44,12 +49,22 @@ class ClientConnection(val client: StarboundClient, type: ConnectionType, uuid:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun flush() {
|
||||||
|
val entries = rpc.write()
|
||||||
|
|
||||||
|
if (entries != null) {
|
||||||
|
channel.write(ClientContextUpdatePacket(entries, KOptional(), KOptional()))
|
||||||
|
}
|
||||||
|
|
||||||
|
super.flush()
|
||||||
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
private val LOGGER = LogManager.getLogger()
|
private val LOGGER = LogManager.getLogger()
|
||||||
|
|
||||||
fun connectToLocalServer(client: StarboundClient, address: LocalAddress, uuid: UUID): ClientConnection {
|
fun connectToLocalServer(client: StarboundClient, address: LocalAddress, uuid: UUID): ClientConnection {
|
||||||
LOGGER.info("Trying to connect to local server at $address with Client UUID $uuid")
|
LOGGER.info("Trying to connect to local server at $address with Client UUID $uuid")
|
||||||
val connection = ClientConnection(client, ConnectionType.MEMORY, uuid)
|
val connection = ClientConnection(client, ConnectionType.MEMORY)
|
||||||
|
|
||||||
Bootstrap()
|
Bootstrap()
|
||||||
.group(NIO_POOL)
|
.group(NIO_POOL)
|
||||||
@ -69,7 +84,7 @@ class ClientConnection(val client: StarboundClient, type: ConnectionType, uuid:
|
|||||||
|
|
||||||
fun connectToRemoteServer(client: StarboundClient, address: SocketAddress, uuid: UUID): ClientConnection {
|
fun connectToRemoteServer(client: StarboundClient, address: SocketAddress, uuid: UUID): ClientConnection {
|
||||||
LOGGER.info("Trying to connect to remote server at $address with Client UUID $uuid")
|
LOGGER.info("Trying to connect to remote server at $address with Client UUID $uuid")
|
||||||
val connection = ClientConnection(client, ConnectionType.NETWORK, uuid)
|
val connection = ClientConnection(client, ConnectionType.NETWORK)
|
||||||
|
|
||||||
Bootstrap()
|
Bootstrap()
|
||||||
.group(NIO_POOL)
|
.group(NIO_POOL)
|
||||||
|
@ -11,19 +11,18 @@ import java.io.DataInputStream
|
|||||||
import java.io.DataOutputStream
|
import java.io.DataOutputStream
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
|
||||||
data class JoinWorldPacket(val uuid: UUID, val seed: Long, val geometry: WorldGeometry) : IClientPacket {
|
data class JoinWorldPacket(val uuid: UUID, val geometry: WorldGeometry) : IClientPacket {
|
||||||
constructor(buff: DataInputStream, isLegacy: Boolean) : this(buff.readUUID(), buff.readLong(), WorldGeometry(buff))
|
constructor(buff: DataInputStream, isLegacy: Boolean) : this(buff.readUUID(), WorldGeometry(buff))
|
||||||
constructor(world: World<*, *>) : this(UUID(0L, 0L), world.seed, world.geometry)
|
constructor(world: World<*, *>) : this(UUID(0L, 0L), world.geometry)
|
||||||
|
|
||||||
override fun write(stream: DataOutputStream, isLegacy: Boolean) {
|
override fun write(stream: DataOutputStream, isLegacy: Boolean) {
|
||||||
stream.writeUUID(uuid)
|
stream.writeUUID(uuid)
|
||||||
stream.writeLong(seed)
|
|
||||||
geometry.write(stream)
|
geometry.write(stream)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun play(connection: ClientConnection) {
|
override fun play(connection: ClientConnection) {
|
||||||
connection.client.mailbox.execute {
|
connection.client.mailbox.execute {
|
||||||
connection.client.world = ClientWorld(connection.client, seed, geometry)
|
connection.client.world = ClientWorld(connection.client, geometry)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -14,11 +14,15 @@ import ru.dbotthepony.kstarbound.client.gl.*
|
|||||||
import ru.dbotthepony.kstarbound.client.gl.shader.UberShader
|
import ru.dbotthepony.kstarbound.client.gl.shader.UberShader
|
||||||
import ru.dbotthepony.kstarbound.client.gl.vertex.*
|
import ru.dbotthepony.kstarbound.client.gl.vertex.*
|
||||||
import ru.dbotthepony.kstarbound.defs.tile.*
|
import ru.dbotthepony.kstarbound.defs.tile.*
|
||||||
|
import ru.dbotthepony.kstarbound.util.random.staticRandom32
|
||||||
|
import ru.dbotthepony.kstarbound.util.random.staticRandomFloat
|
||||||
import ru.dbotthepony.kstarbound.world.api.ITileAccess
|
import ru.dbotthepony.kstarbound.world.api.ITileAccess
|
||||||
import ru.dbotthepony.kstarbound.world.api.AbstractTileState
|
import ru.dbotthepony.kstarbound.world.api.AbstractTileState
|
||||||
import ru.dbotthepony.kstarbound.world.api.TileColor
|
import ru.dbotthepony.kstarbound.world.api.TileColor
|
||||||
import java.time.Duration
|
import java.time.Duration
|
||||||
import java.util.concurrent.Callable
|
import java.util.concurrent.Callable
|
||||||
|
import kotlin.math.absoluteValue
|
||||||
|
import kotlin.math.roundToInt
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Хранит в себе программы для отрисовки определённых [TileDefinition]
|
* Хранит в себе программы для отрисовки определённых [TileDefinition]
|
||||||
@ -150,7 +154,7 @@ class TileRenderer(val renderers: TileRenderers, val def: IRenderableTile) {
|
|||||||
var maxs = piece.texturePosition + piece.textureSize
|
var maxs = piece.texturePosition + piece.textureSize
|
||||||
|
|
||||||
if (def.renderParameters.variants != 0 && piece.variantStride != null && piece.image == null) {
|
if (def.renderParameters.variants != 0 && piece.variantStride != null && piece.image == null) {
|
||||||
val variant = (getter.randomDoubleFor(pos) * def.renderParameters.variants).toInt()
|
val variant = (staticRandomFloat("TileVariant", pos.x, pos.y) * def.renderParameters.variants).roundToInt()
|
||||||
mins += piece.variantStride * variant
|
mins += piece.variantStride * variant
|
||||||
maxs += piece.variantStride * variant
|
maxs += piece.variantStride * variant
|
||||||
}
|
}
|
||||||
|
@ -36,9 +36,8 @@ import kotlin.concurrent.withLock
|
|||||||
|
|
||||||
class ClientWorld(
|
class ClientWorld(
|
||||||
val client: StarboundClient,
|
val client: StarboundClient,
|
||||||
seed: Long,
|
|
||||||
geometry: WorldGeometry,
|
geometry: WorldGeometry,
|
||||||
) : World<ClientWorld, ClientChunk>(seed, geometry) {
|
) : World<ClientWorld, ClientChunk>(geometry) {
|
||||||
private fun determineChunkSize(cells: Int): Int {
|
private fun determineChunkSize(cells: Int): Int {
|
||||||
for (i in 64 downTo 1) {
|
for (i in 64 downTo 1) {
|
||||||
if (cells % i == 0) {
|
if (cells % i == 0) {
|
||||||
|
@ -7,6 +7,7 @@ import com.google.gson.TypeAdapterFactory
|
|||||||
import com.google.gson.reflect.TypeToken
|
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 it.unimi.dsi.fastutil.objects.ObjectArrayList
|
||||||
import ru.dbotthepony.kommons.util.KOptional
|
import ru.dbotthepony.kommons.util.KOptional
|
||||||
import java.lang.reflect.ParameterizedType
|
import java.lang.reflect.ParameterizedType
|
||||||
import java.util.random.RandomGenerator
|
import java.util.random.RandomGenerator
|
||||||
@ -46,6 +47,39 @@ class WeightedList<E>(val parent: ImmutableList<Pair<Double, E>>) {
|
|||||||
return sample(random.nextDouble(sum))
|
return sample(random.nextDouble(sum))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun sample(amount: Int, random: RandomGenerator): List<E> {
|
||||||
|
require(amount >= 0) { "Negative amount to choose: $amount" }
|
||||||
|
if (amount == 0 || isEmpty) return listOf()
|
||||||
|
if (amount == parent.size) return parent.stream().map { it.second }.toList()
|
||||||
|
|
||||||
|
// Original engine is rather lazy, because it creates a **set** of chosen indices
|
||||||
|
// and 'while' loops until it contains desired amount of indices
|
||||||
|
// which means the closer 'amount' is to size of weighted list, the more cpu cycles is burned
|
||||||
|
|
||||||
|
val unseen = ObjectArrayList(parent)
|
||||||
|
val result = ArrayList<E>()
|
||||||
|
var currentSum = sum
|
||||||
|
|
||||||
|
while (unseen.isNotEmpty() && result.size < amount) {
|
||||||
|
val sampled = random.nextDouble(currentSum)
|
||||||
|
val itr = unseen.iterator()
|
||||||
|
var sum = 0.0
|
||||||
|
|
||||||
|
for ((v, e) in itr) {
|
||||||
|
sum += v
|
||||||
|
|
||||||
|
if (sum >= sampled) {
|
||||||
|
result.add(e)
|
||||||
|
itr.remove()
|
||||||
|
currentSum -= v
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
companion object : TypeAdapterFactory {
|
companion object : TypeAdapterFactory {
|
||||||
override fun <T : Any> create(gson: Gson, type: TypeToken<T>): TypeAdapter<T>? {
|
override fun <T : Any> create(gson: Gson, type: TypeToken<T>): TypeAdapter<T>? {
|
||||||
if (type.rawType == WeightedList::class.java) {
|
if (type.rawType == WeightedList::class.java) {
|
||||||
|
@ -9,8 +9,10 @@ import com.google.gson.reflect.TypeToken
|
|||||||
import com.google.gson.stream.JsonReader
|
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 it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap
|
||||||
import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet
|
import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet
|
||||||
import org.apache.logging.log4j.LogManager
|
import org.apache.logging.log4j.LogManager
|
||||||
|
import ru.dbotthepony.kommons.gson.consumeNull
|
||||||
import ru.dbotthepony.kstarbound.Starbound
|
import ru.dbotthepony.kstarbound.Starbound
|
||||||
import ru.dbotthepony.kstarbound.util.AssetPathStack
|
import ru.dbotthepony.kstarbound.util.AssetPathStack
|
||||||
import java.lang.reflect.ParameterizedType
|
import java.lang.reflect.ParameterizedType
|
||||||
@ -27,23 +29,23 @@ data class AssetReference<V>(val path: String?, val fullPath: String?, val value
|
|||||||
if (type.rawType == AssetReference::class.java) {
|
if (type.rawType == AssetReference::class.java) {
|
||||||
val param = type.type as? ParameterizedType ?: return null
|
val param = type.type as? ParameterizedType ?: return null
|
||||||
|
|
||||||
return object : TypeAdapter<AssetReference<Any>>() {
|
return object : TypeAdapter<AssetReference<T>>() {
|
||||||
private val cache = ConcurrentHashMap<String, Pair<Any, JsonElement>>()
|
private val cache = Collections.synchronizedMap(Object2ObjectOpenHashMap<String, Pair<T, JsonElement>>())
|
||||||
private val adapter = gson.getAdapter(TypeToken.get(param.actualTypeArguments[0])) as TypeAdapter<Any>
|
private val adapter = gson.getAdapter(TypeToken.get(param.actualTypeArguments[0])) as TypeAdapter<T>
|
||||||
private val strings = gson.getAdapter(String::class.java)
|
private val strings = gson.getAdapter(String::class.java)
|
||||||
private val jsons = gson.getAdapter(JsonElement::class.java)
|
private val jsons = gson.getAdapter(JsonElement::class.java)
|
||||||
private val missing = Collections.synchronizedSet(ObjectOpenHashSet<String>())
|
private val missing = Collections.synchronizedSet(ObjectOpenHashSet<String>())
|
||||||
private val logger = LogManager.getLogger()
|
private val logger = LogManager.getLogger()
|
||||||
|
|
||||||
override fun write(out: JsonWriter, value: AssetReference<Any>?) {
|
override fun write(out: JsonWriter, value: AssetReference<T>?) {
|
||||||
if (value == null)
|
if (value == null)
|
||||||
out.nullValue()
|
out.nullValue()
|
||||||
else
|
else
|
||||||
out.value(value.fullPath)
|
out.value(value.fullPath)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun read(`in`: JsonReader): AssetReference<Any>? {
|
override fun read(`in`: JsonReader): AssetReference<T>? {
|
||||||
if (`in`.peek() == JsonToken.NULL) {
|
if (`in`.consumeNull()) {
|
||||||
return null
|
return null
|
||||||
} else if (`in`.peek() == JsonToken.STRING) {
|
} else if (`in`.peek() == JsonToken.STRING) {
|
||||||
val path = strings.read(`in`)!!
|
val path = strings.read(`in`)!!
|
||||||
@ -56,21 +58,16 @@ data class AssetReference<V>(val path: String?, val fullPath: String?, val value
|
|||||||
if (fullPath in missing)
|
if (fullPath in missing)
|
||||||
return null
|
return null
|
||||||
|
|
||||||
val file = Starbound.locate(fullPath)
|
val json = Starbound.loadJsonAsset(fullPath)
|
||||||
|
|
||||||
if (!file.exists) {
|
if (json == null) {
|
||||||
logger.error("File does not exist: ${file.computeFullPath()}")
|
logger.error("JSON asset does not exist: $fullPath")
|
||||||
missing.add(fullPath)
|
missing.add(fullPath)
|
||||||
return AssetReference(path, fullPath, null, null)
|
return AssetReference(path, fullPath, null, null)
|
||||||
}
|
}
|
||||||
|
|
||||||
val reader = file.reader()
|
|
||||||
val json = jsons.read(JsonReader(reader).also {
|
|
||||||
it.isLenient = true
|
|
||||||
})
|
|
||||||
|
|
||||||
val value = AssetPathStack(fullPath.substringBefore(':').substringBeforeLast('/')) {
|
val value = AssetPathStack(fullPath.substringBefore(':').substringBeforeLast('/')) {
|
||||||
adapter.read(JsonTreeReader(json))
|
adapter.fromJsonTree(json)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (value == null) {
|
if (value == null) {
|
||||||
|
@ -6,6 +6,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 it.unimi.dsi.fastutil.ints.Int2IntOpenHashMap
|
import it.unimi.dsi.fastutil.ints.Int2IntOpenHashMap
|
||||||
|
import ru.dbotthepony.kommons.gson.consumeNull
|
||||||
|
|
||||||
class ColorReplacements private constructor(private val mapping: Int2IntOpenHashMap) {
|
class ColorReplacements private constructor(private val mapping: Int2IntOpenHashMap) {
|
||||||
constructor(mapping: Map<Int, Int>) : this(Int2IntOpenHashMap(mapping))
|
constructor(mapping: Map<Int, Int>) : this(Int2IntOpenHashMap(mapping))
|
||||||
@ -33,7 +34,7 @@ class ColorReplacements private constructor(private val mapping: Int2IntOpenHash
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun read(`in`: JsonReader): ColorReplacements? {
|
override fun read(`in`: JsonReader): ColorReplacements? {
|
||||||
if (`in`.peek() == JsonToken.NULL)
|
if (`in`.consumeNull())
|
||||||
return null
|
return null
|
||||||
else if (`in`.peek() == JsonToken.STRING) {
|
else if (`in`.peek() == JsonToken.STRING) {
|
||||||
if (`in`.nextString() != "")
|
if (`in`.nextString() != "")
|
||||||
|
@ -9,6 +9,7 @@ import com.google.gson.reflect.TypeToken
|
|||||||
import com.google.gson.stream.JsonReader
|
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.kstarbound.json.builder.JsonImplementation
|
import ru.dbotthepony.kstarbound.json.builder.JsonImplementation
|
||||||
|
|
||||||
@JsonImplementation(ThingDescription::class)
|
@JsonImplementation(ThingDescription::class)
|
||||||
@ -83,6 +84,13 @@ data class ThingDescription(
|
|||||||
val EMPTY = ThingDescription()
|
val EMPTY = ThingDescription()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun fixDescription(newDescription: String): ThingDescription {
|
||||||
|
return copy(
|
||||||
|
shortdescription = if (shortdescription == "...") newDescription else shortdescription,
|
||||||
|
description = if (description == "...") newDescription else description,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
class Factory(val interner: Interner<String> = Interner { it }) : TypeAdapterFactory {
|
class Factory(val interner: Interner<String> = Interner { it }) : TypeAdapterFactory {
|
||||||
override fun <T : Any?> create(gson: Gson, type: TypeToken<T>): TypeAdapter<T>? {
|
override fun <T : Any?> create(gson: Gson, type: TypeToken<T>): TypeAdapter<T>? {
|
||||||
if (type.rawType == ThingDescription::class.java) {
|
if (type.rawType == ThingDescription::class.java) {
|
||||||
@ -114,13 +122,13 @@ data class ThingDescription(
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun read(`in`: JsonReader): ThingDescription? {
|
override fun read(`in`: JsonReader): ThingDescription? {
|
||||||
if (`in`.peek() == JsonToken.NULL)
|
if (`in`.consumeNull())
|
||||||
return null
|
return null
|
||||||
|
|
||||||
`in`.beginObject()
|
`in`.beginObject()
|
||||||
|
|
||||||
var shortdescription = "..."
|
var shortdescription: String? = null
|
||||||
var description = "..."
|
var description: String? = null
|
||||||
val racial = ImmutableMap.Builder<String, String>()
|
val racial = ImmutableMap.Builder<String, String>()
|
||||||
val racialShort = ImmutableMap.Builder<String, String>()
|
val racialShort = ImmutableMap.Builder<String, String>()
|
||||||
|
|
||||||
@ -146,9 +154,18 @@ data class ThingDescription(
|
|||||||
|
|
||||||
`in`.endObject()
|
`in`.endObject()
|
||||||
|
|
||||||
|
if (shortdescription == null && description == null) {
|
||||||
|
shortdescription = "..."
|
||||||
|
description = "..."
|
||||||
|
} else if (shortdescription == null) {
|
||||||
|
shortdescription = description
|
||||||
|
} else if (description == null) {
|
||||||
|
description = shortdescription
|
||||||
|
}
|
||||||
|
|
||||||
return ThingDescription(
|
return ThingDescription(
|
||||||
shortdescription = shortdescription,
|
shortdescription = shortdescription!!,
|
||||||
description = description,
|
description = description!!,
|
||||||
racialDescription = racial.build(),
|
racialDescription = racial.build(),
|
||||||
racialShortDescription = racialShort.build()
|
racialShortDescription = racialShort.build()
|
||||||
)
|
)
|
||||||
|
@ -36,8 +36,8 @@ data class ItemReference(
|
|||||||
override fun <T : Any> create(gson: Gson, type: TypeToken<T>): TypeAdapter<T>? {
|
override fun <T : Any> create(gson: Gson, type: TypeToken<T>): TypeAdapter<T>? {
|
||||||
if (type.rawType == ItemReference::class.java) {
|
if (type.rawType == ItemReference::class.java) {
|
||||||
return object : TypeAdapter<ItemReference>() {
|
return object : TypeAdapter<ItemReference>() {
|
||||||
private val regularObject = FactoryAdapter.createFor(ItemReference::class, JsonFactory(storesJson = false, asList = false), gson, stringInterner)
|
private val regularObject = FactoryAdapter.createFor(ItemReference::class, JsonFactory(asList = false), gson, stringInterner)
|
||||||
private val regularList = FactoryAdapter.createFor(ItemReference::class, JsonFactory(storesJson = false, asList = true), gson, stringInterner)
|
private val regularList = FactoryAdapter.createFor(ItemReference::class, JsonFactory(asList = true), gson, stringInterner)
|
||||||
private val references = gson.getAdapter(TypeToken.getParameterized(Registry.Ref::class.java, IItemDefinition::class.java)) as TypeAdapter<Registry.Ref<IItemDefinition>>
|
private val references = gson.getAdapter(TypeToken.getParameterized(Registry.Ref::class.java, IItemDefinition::class.java)) as TypeAdapter<Registry.Ref<IItemDefinition>>
|
||||||
|
|
||||||
override fun write(out: JsonWriter, value: ItemReference?) {
|
override fun write(out: JsonWriter, value: ItemReference?) {
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
package ru.dbotthepony.kstarbound.defs
|
package ru.dbotthepony.kstarbound.defs
|
||||||
|
|
||||||
|
import com.google.gson.JsonArray
|
||||||
import com.google.gson.JsonElement
|
import com.google.gson.JsonElement
|
||||||
import com.google.gson.JsonObject
|
import com.google.gson.JsonObject
|
||||||
import com.google.gson.TypeAdapter
|
import com.google.gson.TypeAdapter
|
||||||
@ -219,10 +220,10 @@ abstract class JsonDriven(val path: String) {
|
|||||||
for ((k, v) in b.entrySet()) {
|
for ((k, v) in b.entrySet()) {
|
||||||
val existing = a[k]
|
val existing = a[k]
|
||||||
|
|
||||||
if (existing == null) {
|
if (existing is JsonObject && v is JsonObject) {
|
||||||
a[k] = v.deepCopy()
|
|
||||||
} else if (existing is JsonObject && v is JsonObject) {
|
|
||||||
a[k] = mergeNoCopy(existing, v)
|
a[k] = mergeNoCopy(existing, v)
|
||||||
|
} else if (existing !is JsonObject) {
|
||||||
|
a[k] = v.deepCopy()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3,6 +3,7 @@ package ru.dbotthepony.kstarbound.defs
|
|||||||
import com.google.common.collect.ImmutableList
|
import com.google.common.collect.ImmutableList
|
||||||
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.JsonSyntaxException
|
import com.google.gson.JsonSyntaxException
|
||||||
import com.google.gson.TypeAdapter
|
import com.google.gson.TypeAdapter
|
||||||
import com.google.gson.TypeAdapterFactory
|
import com.google.gson.TypeAdapterFactory
|
||||||
@ -11,8 +12,11 @@ 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.Vector2dTypeAdapter
|
import ru.dbotthepony.kommons.gson.Vector2dTypeAdapter
|
||||||
|
import ru.dbotthepony.kommons.gson.consumeNull
|
||||||
import ru.dbotthepony.kommons.math.linearInterpolation
|
import ru.dbotthepony.kommons.math.linearInterpolation
|
||||||
|
import ru.dbotthepony.kommons.util.KOptional
|
||||||
import ru.dbotthepony.kommons.vector.Vector2d
|
import ru.dbotthepony.kommons.vector.Vector2d
|
||||||
|
import ru.dbotthepony.kstarbound.Starbound
|
||||||
import ru.dbotthepony.kstarbound.json.builder.EnumAdapter
|
import ru.dbotthepony.kstarbound.json.builder.EnumAdapter
|
||||||
|
|
||||||
enum class JsonFunctionInterpolation {
|
enum class JsonFunctionInterpolation {
|
||||||
@ -110,7 +114,7 @@ class JsonFunction(
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun read(reader: JsonReader): JsonFunction? {
|
override fun read(reader: JsonReader): JsonFunction? {
|
||||||
if (reader.peek() == JsonToken.NULL)
|
if (reader.consumeNull())
|
||||||
return null
|
return null
|
||||||
|
|
||||||
reader.beginArray()
|
reader.beginArray()
|
||||||
@ -232,7 +236,7 @@ class Json2Function(
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun read(reader: JsonReader): Json2Function? {
|
override fun read(reader: JsonReader): Json2Function? {
|
||||||
if (reader.peek() == JsonToken.NULL)
|
if (reader.consumeNull())
|
||||||
return null
|
return null
|
||||||
|
|
||||||
reader.beginArray()
|
reader.beginArray()
|
||||||
@ -323,3 +327,44 @@ class Json2Function(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class JsonConfigFunction(val data: ImmutableList<Pair<Double, JsonElement>>) {
|
||||||
|
fun evaluate(point: Double): JsonElement? {
|
||||||
|
return data.lastOrNull { it.first <= point }?.second
|
||||||
|
}
|
||||||
|
|
||||||
|
fun <T> evaluate(point: Double, adapter: TypeAdapter<T>): KOptional<T> {
|
||||||
|
val eval = data.lastOrNull { it.first <= point }?.second ?: return KOptional()
|
||||||
|
return KOptional(adapter.fromJsonTree(eval))
|
||||||
|
}
|
||||||
|
|
||||||
|
class Adapter(gson: Gson) : TypeAdapter<JsonConfigFunction>() {
|
||||||
|
val parent = gson.getAdapter(
|
||||||
|
TypeToken.getParameterized(
|
||||||
|
ImmutableList::class.java,
|
||||||
|
TypeToken.getParameterized(Pair::class.java,
|
||||||
|
java.lang.Double::class.java,
|
||||||
|
JsonElement::class.java
|
||||||
|
).type
|
||||||
|
)
|
||||||
|
) as TypeAdapter<ImmutableList<Pair<Double, JsonElement>>>
|
||||||
|
|
||||||
|
override fun write(out: JsonWriter, value: JsonConfigFunction?) {
|
||||||
|
if (value == null)
|
||||||
|
out.nullValue()
|
||||||
|
else
|
||||||
|
parent.write(out, value.data)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun read(`in`: JsonReader): JsonConfigFunction? {
|
||||||
|
if (`in`.consumeNull())
|
||||||
|
return null
|
||||||
|
|
||||||
|
return JsonConfigFunction(
|
||||||
|
parent.read(`in`)
|
||||||
|
.stream()
|
||||||
|
.sorted { o1, o2 -> o1.first.compareTo(o2.first) }
|
||||||
|
.collect(ImmutableList.toImmutableList()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -17,6 +17,8 @@ import ru.dbotthepony.kommons.gson.consumeNull
|
|||||||
import ru.dbotthepony.kommons.gson.value
|
import ru.dbotthepony.kommons.gson.value
|
||||||
import ru.dbotthepony.kstarbound.util.AssetPathStack
|
import ru.dbotthepony.kstarbound.util.AssetPathStack
|
||||||
|
|
||||||
|
@Deprecated("Don't use directly, use one of subtypes instead")
|
||||||
|
@Suppress("DEPRECATION")
|
||||||
sealed class JsonReference<E : JsonElement?>(val path: String?, val fullPath: String?) {
|
sealed class JsonReference<E : JsonElement?>(val path: String?, val fullPath: String?) {
|
||||||
abstract val value: E
|
abstract val value: E
|
||||||
|
|
||||||
|
@ -1,12 +1,14 @@
|
|||||||
package ru.dbotthepony.kstarbound.defs
|
package ru.dbotthepony.kstarbound.defs
|
||||||
|
|
||||||
|
import com.google.gson.stream.JsonWriter
|
||||||
|
import ru.dbotthepony.kstarbound.json.builder.IStringSerializable
|
||||||
import ru.dbotthepony.kstarbound.json.builder.JsonFactory
|
import ru.dbotthepony.kstarbound.json.builder.JsonFactory
|
||||||
|
|
||||||
@JsonFactory
|
@JsonFactory
|
||||||
data class PerlinNoiseParameters(
|
data class PerlinNoiseParameters(
|
||||||
val type: Type = Type.PERLIN,
|
val type: Type = Type.PERLIN,
|
||||||
val seed: Long? = null,
|
val seed: Long? = null,
|
||||||
val scale: Int = 512,
|
val scale: Int = DEFAULT_SCALE,
|
||||||
val octaves: Int = 1,
|
val octaves: Int = 1,
|
||||||
val gain: Double = 2.0,
|
val gain: Double = 2.0,
|
||||||
val offset: Double = 1.0,
|
val offset: Double = 1.0,
|
||||||
@ -20,9 +22,21 @@ data class PerlinNoiseParameters(
|
|||||||
require(scale >= 16) { "Too little perlin noise scale" }
|
require(scale >= 16) { "Too little perlin noise scale" }
|
||||||
}
|
}
|
||||||
|
|
||||||
enum class Type {
|
enum class Type(val jsonName: String) : IStringSerializable {
|
||||||
PERLIN,
|
PERLIN("perlin"),
|
||||||
BILLOW,
|
BILLOW("billow"),
|
||||||
RIDGED_MULTI;
|
RIDGED_MULTI("ridgedmulti");
|
||||||
|
|
||||||
|
override fun match(name: String): Boolean {
|
||||||
|
return name.lowercase() == jsonName
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun write(out: JsonWriter) {
|
||||||
|
out.value(jsonName)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
const val DEFAULT_SCALE = 512
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -302,7 +302,7 @@ class Image private constructor(
|
|||||||
private val objects by lazy { Starbound.gson.getAdapter(JsonObject::class.java) }
|
private val objects by lazy { Starbound.gson.getAdapter(JsonObject::class.java) }
|
||||||
private val vectors by lazy { Starbound.gson.getAdapter(Vector4i::class.java) }
|
private val vectors by lazy { Starbound.gson.getAdapter(Vector4i::class.java) }
|
||||||
private val vectors2 by lazy { Starbound.gson.getAdapter(Vector2i::class.java) }
|
private val vectors2 by lazy { Starbound.gson.getAdapter(Vector2i::class.java) }
|
||||||
private val cache = ConcurrentHashMap<String, Optional<List<DataSprite>>>()
|
private val configCache = ConcurrentHashMap<String, Optional<List<DataSprite>>>()
|
||||||
private val imageCache = ConcurrentHashMap<String, Optional<Image>>()
|
private val imageCache = ConcurrentHashMap<String, Optional<Image>>()
|
||||||
private val logger = LogManager.getLogger()
|
private val logger = LogManager.getLogger()
|
||||||
|
|
||||||
@ -505,7 +505,7 @@ class Image private constructor(
|
|||||||
val name = path.substringBefore(':').substringAfterLast('/').substringBefore('.')
|
val name = path.substringBefore(':').substringAfterLast('/').substringBefore('.')
|
||||||
|
|
||||||
while (true) {
|
while (true) {
|
||||||
val find = cache.computeIfAbsent("$folder/$name", ::compute).or { cache.computeIfAbsent("$folder/default", ::compute) }
|
val find = configCache.computeIfAbsent("$folder/$name", ::compute).or { configCache.computeIfAbsent("$folder/default", ::compute) }
|
||||||
|
|
||||||
if (find.isPresent)
|
if (find.isPresent)
|
||||||
return find.get()
|
return find.get()
|
||||||
|
@ -6,6 +6,7 @@ import com.google.gson.internal.bind.JsonTreeReader
|
|||||||
import com.google.gson.stream.JsonReader
|
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.kstarbound.Starbound
|
import ru.dbotthepony.kstarbound.Starbound
|
||||||
import ru.dbotthepony.kstarbound.defs.image.SpriteReference
|
import ru.dbotthepony.kstarbound.defs.image.SpriteReference
|
||||||
import ru.dbotthepony.kstarbound.json.builder.FactoryAdapter
|
import ru.dbotthepony.kstarbound.json.builder.FactoryAdapter
|
||||||
@ -27,7 +28,7 @@ data class InventoryIcon(
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun read(`in`: JsonReader): InventoryIcon? {
|
override fun read(`in`: JsonReader): InventoryIcon? {
|
||||||
if (`in`.peek() == JsonToken.NULL)
|
if (`in`.consumeNull())
|
||||||
return null
|
return null
|
||||||
|
|
||||||
if (`in`.peek() == JsonToken.STRING) {
|
if (`in`.peek() == JsonToken.STRING) {
|
||||||
|
@ -7,6 +7,7 @@ import com.google.gson.reflect.TypeToken
|
|||||||
import com.google.gson.stream.JsonReader
|
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.kstarbound.defs.image.SpriteReference
|
import ru.dbotthepony.kstarbound.defs.image.SpriteReference
|
||||||
import ru.dbotthepony.kstarbound.json.builder.FactoryAdapter
|
import ru.dbotthepony.kstarbound.json.builder.FactoryAdapter
|
||||||
import ru.dbotthepony.kstarbound.json.builder.JsonFactory
|
import ru.dbotthepony.kstarbound.json.builder.JsonFactory
|
||||||
@ -53,7 +54,7 @@ interface IArmorItemDefinition : ILeveledItemDefinition, IScriptableItemDefiniti
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun read(`in`: JsonReader): Frames? {
|
override fun read(`in`: JsonReader): Frames? {
|
||||||
if (`in`.peek() == JsonToken.NULL)
|
if (`in`.consumeNull())
|
||||||
return null
|
return null
|
||||||
else if (`in`.peek() == JsonToken.STRING)
|
else if (`in`.peek() == JsonToken.STRING)
|
||||||
return Frames(frames.read(`in`), null, null)
|
return Frames(frames.read(`in`), null, null)
|
||||||
|
@ -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 it.unimi.dsi.fastutil.ints.Int2ObjectArrayMap
|
import it.unimi.dsi.fastutil.ints.Int2ObjectArrayMap
|
||||||
|
import ru.dbotthepony.kommons.gson.consumeNull
|
||||||
import ru.dbotthepony.kstarbound.Registry
|
import ru.dbotthepony.kstarbound.Registry
|
||||||
import ru.dbotthepony.kstarbound.defs.item.api.IItemDefinition
|
import ru.dbotthepony.kstarbound.defs.item.api.IItemDefinition
|
||||||
import ru.dbotthepony.kstarbound.json.builder.JsonFactory
|
import ru.dbotthepony.kstarbound.json.builder.JsonFactory
|
||||||
@ -48,7 +49,7 @@ class BlueprintLearnList private constructor(private val tiers: Int2ObjectArrayM
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun read(`in`: JsonReader): BlueprintLearnList? {
|
override fun read(`in`: JsonReader): BlueprintLearnList? {
|
||||||
if (`in`.peek() == JsonToken.NULL)
|
if (`in`.consumeNull())
|
||||||
return null
|
return null
|
||||||
|
|
||||||
val tiers = Int2ObjectArrayMap<ImmutableList<Entry>>()
|
val tiers = Int2ObjectArrayMap<ImmutableList<Entry>>()
|
||||||
|
@ -23,8 +23,8 @@ object BuiltinMetaMaterials {
|
|||||||
damageFactors = Object2DoubleMaps.emptyMap(),
|
damageFactors = Object2DoubleMaps.emptyMap(),
|
||||||
damageRecovery = 1.0,
|
damageRecovery = 1.0,
|
||||||
maximumEffectTime = 0.0,
|
maximumEffectTime = 0.0,
|
||||||
health = null,
|
totalHealth = Double.MAX_VALUE,
|
||||||
harvestLevel = null,
|
harvestLevel = Int.MAX_VALUE,
|
||||||
)
|
)
|
||||||
))
|
))
|
||||||
|
|
||||||
|
@ -5,10 +5,10 @@ import it.unimi.dsi.fastutil.objects.Object2DoubleMaps
|
|||||||
import ru.dbotthepony.kstarbound.json.builder.JsonFactory
|
import ru.dbotthepony.kstarbound.json.builder.JsonFactory
|
||||||
|
|
||||||
@JsonFactory
|
@JsonFactory
|
||||||
class TileDamageConfig(
|
data class TileDamageConfig(
|
||||||
val damageFactors: Object2DoubleMap<String> = Object2DoubleMaps.emptyMap(),
|
val damageFactors: Object2DoubleMap<String> = Object2DoubleMaps.emptyMap(),
|
||||||
val damageRecovery: Double = 1.0,
|
val damageRecovery: Double = 1.0,
|
||||||
val maximumEffectTime: Double = 0.0,
|
val maximumEffectTime: Double = 1.5,
|
||||||
val health: Double? = null,
|
val totalHealth: Double = 1.0,
|
||||||
val harvestLevel: Int? = null
|
val harvestLevel: Int = 1,
|
||||||
)
|
)
|
||||||
|
@ -0,0 +1,11 @@
|
|||||||
|
package ru.dbotthepony.kstarbound.defs.world
|
||||||
|
|
||||||
|
import com.google.common.collect.ImmutableList
|
||||||
|
import ru.dbotthepony.kstarbound.defs.AssetPath
|
||||||
|
import ru.dbotthepony.kstarbound.json.builder.JsonFactory
|
||||||
|
|
||||||
|
@JsonFactory
|
||||||
|
data class AmbientNoisesDefinition(val day: Group? = null, val night: Group? = null) {
|
||||||
|
@JsonFactory
|
||||||
|
data class Group(val tracks: ImmutableList<AssetPath>)
|
||||||
|
}
|
@ -0,0 +1,46 @@
|
|||||||
|
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 ru.dbotthepony.kommons.math.RGBAColor
|
||||||
|
import ru.dbotthepony.kommons.vector.Vector2d
|
||||||
|
import ru.dbotthepony.kommons.vector.Vector2i
|
||||||
|
import ru.dbotthepony.kstarbound.json.builder.JsonFactory
|
||||||
|
import ru.dbotthepony.kstarbound.util.random.AbstractPerlinNoise
|
||||||
|
|
||||||
|
@JsonFactory
|
||||||
|
data class AsteroidWorldsConfig(
|
||||||
|
val biome: String,
|
||||||
|
val gravityRange: Vector2d,
|
||||||
|
val worldSize: Vector2i,
|
||||||
|
val threatRange: Vector2d,
|
||||||
|
val environmentStatusEffects: ImmutableSet<String> = ImmutableSet.of(),
|
||||||
|
val overrideTech: ImmutableSet<String>? = null,
|
||||||
|
val globalDirectives: ImmutableSet<String>? = null,
|
||||||
|
val beamUpRule: BeamUpRule = BeamUpRule.SURFACE, // TODO: ??? why surface? in asteroid field.
|
||||||
|
val disableDeathDrops: Boolean = false,
|
||||||
|
val worldEdgeForceRegions: WorldEdgeForceRegion = WorldEdgeForceRegion.TOP_AND_BOTTOM,
|
||||||
|
val asteroidsTop: Int,
|
||||||
|
val asteroidsBottom: Int,
|
||||||
|
val ambientLightLevel: RGBAColor,
|
||||||
|
val blendSize: Double,
|
||||||
|
|
||||||
|
val blockNoise: BlockNoiseConfig? = null,
|
||||||
|
val terrains: ImmutableList<Terrain>,
|
||||||
|
val emptyTerrain: Terrain,
|
||||||
|
) {
|
||||||
|
@JsonFactory
|
||||||
|
data class Terrain(
|
||||||
|
val terrainSelector: String,
|
||||||
|
val caveSelector: String,
|
||||||
|
val bgCaveSelector: String,
|
||||||
|
val oreSelector: String,
|
||||||
|
val subBlockSelector: String,
|
||||||
|
)
|
||||||
|
|
||||||
|
init {
|
||||||
|
require(worldSize.x > 0) { "Invalid asteroid worlds width: ${worldSize.x}" }
|
||||||
|
require(worldSize.y > 0) { "Invalid asteroid worlds height: ${worldSize.y}" }
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,147 @@
|
|||||||
|
package ru.dbotthepony.kstarbound.defs.world
|
||||||
|
|
||||||
|
import com.google.gson.JsonObject
|
||||||
|
import ru.dbotthepony.kommons.gson.set
|
||||||
|
import ru.dbotthepony.kommons.math.RGBAColor
|
||||||
|
import ru.dbotthepony.kommons.util.AABBi
|
||||||
|
import ru.dbotthepony.kommons.vector.Vector2d
|
||||||
|
import ru.dbotthepony.kommons.vector.Vector2i
|
||||||
|
import ru.dbotthepony.kstarbound.GlobalDefaults
|
||||||
|
import ru.dbotthepony.kstarbound.Starbound
|
||||||
|
import ru.dbotthepony.kstarbound.fromJson
|
||||||
|
import ru.dbotthepony.kstarbound.json.builder.JsonFactory
|
||||||
|
import ru.dbotthepony.kstarbound.util.random.nextRange
|
||||||
|
import ru.dbotthepony.kstarbound.util.random.random
|
||||||
|
import kotlin.properties.Delegates
|
||||||
|
|
||||||
|
class AsteroidsWorldParameters : VisitableWorldParameters() {
|
||||||
|
var asteroidTopLevel: Int = 0
|
||||||
|
private set
|
||||||
|
var asteroidBottomLevel: Int = 0
|
||||||
|
private set
|
||||||
|
var blendSize: Double = 0.0
|
||||||
|
private set
|
||||||
|
var asteroidBiome: String by Delegates.notNull()
|
||||||
|
private set
|
||||||
|
var ambientLightLevel: RGBAColor by Delegates.notNull()
|
||||||
|
private set
|
||||||
|
|
||||||
|
init {
|
||||||
|
airless = true
|
||||||
|
}
|
||||||
|
|
||||||
|
override val type: VisitableWorldParametersType
|
||||||
|
get() = VisitableWorldParametersType.ASTEROIDS
|
||||||
|
|
||||||
|
@JsonFactory
|
||||||
|
data class StoreData(
|
||||||
|
val asteroidTopLevel: Int = 0,
|
||||||
|
val asteroidBottomLevel: Int = 0,
|
||||||
|
val blendSize: Double = 0.0,
|
||||||
|
val asteroidBiome: String,
|
||||||
|
val ambientLightLevel: RGBAColor,
|
||||||
|
)
|
||||||
|
|
||||||
|
override fun fromJson(data: JsonObject) {
|
||||||
|
super.fromJson(data)
|
||||||
|
|
||||||
|
val store = Starbound.gson.fromJson(data, StoreData::class.java)
|
||||||
|
asteroidTopLevel = store.asteroidTopLevel
|
||||||
|
asteroidBottomLevel = store.asteroidBottomLevel
|
||||||
|
blendSize = store.blendSize
|
||||||
|
asteroidBiome = store.asteroidBiome
|
||||||
|
ambientLightLevel = store.ambientLightLevel
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun toJson(data: JsonObject, isLegacy: Boolean) {
|
||||||
|
super.toJson(data, isLegacy)
|
||||||
|
|
||||||
|
val store = Starbound.gson.toJsonTree(StoreData(
|
||||||
|
asteroidTopLevel = asteroidTopLevel,
|
||||||
|
asteroidBottomLevel = asteroidBottomLevel,
|
||||||
|
blendSize = blendSize,
|
||||||
|
asteroidBiome = asteroidBiome,
|
||||||
|
ambientLightLevel = ambientLightLevel,
|
||||||
|
)) as JsonObject
|
||||||
|
|
||||||
|
for ((k, v) in store.entrySet())
|
||||||
|
data[k] = v
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun createLayout(seed: Long): WorldLayout {
|
||||||
|
val random = random(seed)
|
||||||
|
val terrain = GlobalDefaults.asteroidWorlds.terrains.random(random)
|
||||||
|
|
||||||
|
val layout = WorldLayout()
|
||||||
|
layout.worldSize = worldSize
|
||||||
|
|
||||||
|
val asteroidRegion = WorldLayout.RegionParameters(
|
||||||
|
worldSize.y / 2,
|
||||||
|
threatLevel,
|
||||||
|
asteroidBiome,
|
||||||
|
terrain.terrainSelector,
|
||||||
|
terrain.caveSelector,
|
||||||
|
terrain.bgCaveSelector,
|
||||||
|
terrain.oreSelector,
|
||||||
|
terrain.oreSelector,
|
||||||
|
terrain.subBlockSelector,
|
||||||
|
WorldLayout.RegionLiquids(),
|
||||||
|
)
|
||||||
|
|
||||||
|
val emptyRegion = WorldLayout.RegionParameters(
|
||||||
|
worldSize.y / 2,
|
||||||
|
threatLevel,
|
||||||
|
asteroidBiome,
|
||||||
|
GlobalDefaults.asteroidWorlds.emptyTerrain.terrainSelector,
|
||||||
|
GlobalDefaults.asteroidWorlds.emptyTerrain.caveSelector,
|
||||||
|
GlobalDefaults.asteroidWorlds.emptyTerrain.bgCaveSelector,
|
||||||
|
GlobalDefaults.asteroidWorlds.emptyTerrain.oreSelector,
|
||||||
|
GlobalDefaults.asteroidWorlds.emptyTerrain.oreSelector,
|
||||||
|
GlobalDefaults.asteroidWorlds.emptyTerrain.subBlockSelector,
|
||||||
|
WorldLayout.RegionLiquids(),
|
||||||
|
)
|
||||||
|
|
||||||
|
layout.addLayer(random, 0, emptyRegion)
|
||||||
|
layout.addLayer(random, asteroidBottomLevel, asteroidRegion)
|
||||||
|
layout.addLayer(random, asteroidTopLevel, emptyRegion)
|
||||||
|
|
||||||
|
layout.regionBlending = blendSize
|
||||||
|
layout.blockNoise = GlobalDefaults.asteroidWorlds.blockNoise?.build(random)
|
||||||
|
|
||||||
|
layout.playerStartSearchRegions.add(
|
||||||
|
AABBi(
|
||||||
|
Vector2i(0, asteroidBottomLevel),
|
||||||
|
Vector2i(worldSize.x, asteroidTopLevel),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
layout.finalize(RGBAColor.BLACK)
|
||||||
|
return layout
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
fun generate(seed: Long): AsteroidsWorldParameters {
|
||||||
|
val random = random(seed)
|
||||||
|
|
||||||
|
val parameters = AsteroidsWorldParameters()
|
||||||
|
|
||||||
|
parameters.threatLevel = random.nextRange(GlobalDefaults.asteroidWorlds.threatRange)
|
||||||
|
parameters.typeName = "asteroids"
|
||||||
|
parameters.worldSize = GlobalDefaults.asteroidWorlds.worldSize
|
||||||
|
parameters.gravity = Vector2d(y = random.nextRange(GlobalDefaults.asteroidWorlds.gravityRange))
|
||||||
|
parameters.environmentStatusEffects = GlobalDefaults.asteroidWorlds.environmentStatusEffects
|
||||||
|
parameters.overrideTech = GlobalDefaults.asteroidWorlds.overrideTech
|
||||||
|
parameters.globalDirectives = GlobalDefaults.asteroidWorlds.globalDirectives
|
||||||
|
parameters.beamUpRule = GlobalDefaults.asteroidWorlds.beamUpRule
|
||||||
|
parameters.disableDeathDrops = GlobalDefaults.asteroidWorlds.disableDeathDrops
|
||||||
|
parameters.worldEdgeForceRegions = GlobalDefaults.asteroidWorlds.worldEdgeForceRegions
|
||||||
|
parameters.asteroidTopLevel = GlobalDefaults.asteroidWorlds.asteroidsTop
|
||||||
|
parameters.asteroidBottomLevel = GlobalDefaults.asteroidWorlds.asteroidsBottom
|
||||||
|
parameters.blendSize = GlobalDefaults.asteroidWorlds.blendSize
|
||||||
|
parameters.ambientLightLevel = GlobalDefaults.asteroidWorlds.ambientLightLevel
|
||||||
|
parameters.asteroidBiome = GlobalDefaults.asteroidWorlds.biome
|
||||||
|
|
||||||
|
return parameters
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,75 @@
|
|||||||
|
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.JsonElement
|
||||||
|
import ru.dbotthepony.kstarbound.GlobalDefaults
|
||||||
|
import ru.dbotthepony.kstarbound.Registries
|
||||||
|
import ru.dbotthepony.kstarbound.Registry
|
||||||
|
import ru.dbotthepony.kstarbound.defs.AssetReference
|
||||||
|
import ru.dbotthepony.kstarbound.defs.ThingDescription
|
||||||
|
import ru.dbotthepony.kstarbound.defs.tile.TileDamageConfig
|
||||||
|
import ru.dbotthepony.kstarbound.json.builder.JsonFactory
|
||||||
|
import ru.dbotthepony.kstarbound.json.builder.JsonFlat
|
||||||
|
|
||||||
|
@JsonFactory
|
||||||
|
class BushVariant(
|
||||||
|
val bushName: String,
|
||||||
|
val modName: String,
|
||||||
|
|
||||||
|
// because shapes don't contain absolute paths.
|
||||||
|
// god damn it
|
||||||
|
val directory: String,
|
||||||
|
val shapes: ImmutableList<Shape>,
|
||||||
|
|
||||||
|
val baseHueShift: Double,
|
||||||
|
val modHueShift: Double,
|
||||||
|
|
||||||
|
@JsonFlat
|
||||||
|
val descriptions: ThingDescription,
|
||||||
|
val ceiling: Boolean,
|
||||||
|
|
||||||
|
val ephemeral: Boolean,
|
||||||
|
val tileDamageParameters: TileDamageConfig,
|
||||||
|
) {
|
||||||
|
@JsonFactory
|
||||||
|
data class Shape(val image: String, val mods: ImmutableList<String>)
|
||||||
|
|
||||||
|
@JsonFactory
|
||||||
|
data class DataShape(val base: String, val mods: ImmutableMap<String, ImmutableList<String>> = ImmutableMap.of())
|
||||||
|
|
||||||
|
@JsonFactory
|
||||||
|
data class Data(
|
||||||
|
val name: String,
|
||||||
|
@JsonFlat
|
||||||
|
val descriptions: ThingDescription,
|
||||||
|
val shapes: ImmutableList<DataShape>,
|
||||||
|
val mods: ImmutableSet<String> = ImmutableSet.of(),
|
||||||
|
val ceiling: Boolean = false,
|
||||||
|
val ephemeral: Boolean = true,
|
||||||
|
val damageTable: AssetReference<TileDamageConfig>? = null,
|
||||||
|
val health: Double = 1.0,
|
||||||
|
)
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
fun create(bushName: String, baseHueShift: Double, modName: String, modHueShift: Double): BushVariant {
|
||||||
|
return create(Registries.bushVariants.getOrThrow(bushName), baseHueShift, modName, modHueShift)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun create(data: Registry.Entry<Data>, baseHueShift: Double, modName: String, modHueShift: Double): BushVariant {
|
||||||
|
return BushVariant(
|
||||||
|
bushName = data.key,
|
||||||
|
baseHueShift = baseHueShift,
|
||||||
|
directory = data.file?.computeDirectory() ?: "/",
|
||||||
|
modHueShift = modHueShift,
|
||||||
|
ceiling = data.value.ceiling,
|
||||||
|
descriptions = data.value.descriptions.fixDescription("${data.key} with $modName"),
|
||||||
|
ephemeral = data.value.ephemeral,
|
||||||
|
tileDamageParameters = (data.value.damageTable?.value ?: GlobalDefaults.bushDamage).copy(totalHealth = data.value.health),
|
||||||
|
modName = modName,
|
||||||
|
shapes = data.value.shapes.stream().map { Shape(it.base, it.mods[modName] ?: ImmutableList.of()) }.collect(ImmutableList.toImmutableList())
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,33 @@
|
|||||||
|
package ru.dbotthepony.kstarbound.defs.world
|
||||||
|
|
||||||
|
import ru.dbotthepony.kommons.io.readVector2i
|
||||||
|
import ru.dbotthepony.kommons.io.writeStruct2i
|
||||||
|
import ru.dbotthepony.kommons.vector.Vector2i
|
||||||
|
import ru.dbotthepony.kstarbound.json.builder.JsonFactory
|
||||||
|
import java.io.DataInputStream
|
||||||
|
import java.io.DataOutputStream
|
||||||
|
|
||||||
|
@JsonFactory
|
||||||
|
data class CelestialBaseInformation(
|
||||||
|
val planetOrbitalLevels: Int = 1,
|
||||||
|
val satelliteOrbitalLevels: Int = 1,
|
||||||
|
val chunkSize: Int = 1,
|
||||||
|
val xyCoordRange: Vector2i = Vector2i.ZERO,
|
||||||
|
val zCoordRange: Vector2i = Vector2i.ZERO,
|
||||||
|
) {
|
||||||
|
constructor(stream: DataInputStream, isLegacy: Boolean) : this(
|
||||||
|
stream.readInt(),
|
||||||
|
stream.readInt(),
|
||||||
|
stream.readInt(),
|
||||||
|
stream.readVector2i(),
|
||||||
|
stream.readVector2i(),
|
||||||
|
)
|
||||||
|
|
||||||
|
fun write(stream: DataOutputStream, isLegacy: Boolean) {
|
||||||
|
stream.writeInt(planetOrbitalLevels)
|
||||||
|
stream.writeInt(satelliteOrbitalLevels)
|
||||||
|
stream.writeInt(chunkSize)
|
||||||
|
stream.writeStruct2i(xyCoordRange)
|
||||||
|
stream.writeStruct2i(zCoordRange)
|
||||||
|
}
|
||||||
|
}
|
@ -1,64 +1,11 @@
|
|||||||
package ru.dbotthepony.kstarbound.defs
|
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.ImmutableMap
|
||||||
import com.google.gson.JsonObject
|
import com.google.gson.JsonObject
|
||||||
import it.unimi.dsi.fastutil.ints.Int2ObjectMap
|
|
||||||
import ru.dbotthepony.kommons.io.readVector2i
|
|
||||||
import ru.dbotthepony.kommons.io.writeStruct2i
|
|
||||||
import ru.dbotthepony.kommons.vector.Vector2i
|
import ru.dbotthepony.kommons.vector.Vector2i
|
||||||
import ru.dbotthepony.kstarbound.collect.WeightedList
|
|
||||||
import ru.dbotthepony.kstarbound.json.builder.JsonFactory
|
import ru.dbotthepony.kstarbound.json.builder.JsonFactory
|
||||||
import ru.dbotthepony.kstarbound.util.random.AbstractPerlinNoise
|
import ru.dbotthepony.kstarbound.util.random.AbstractPerlinNoise
|
||||||
import ru.dbotthepony.kstarbound.world.UniversePos
|
|
||||||
import java.io.DataInputStream
|
|
||||||
import java.io.DataOutputStream
|
|
||||||
|
|
||||||
@JsonFactory
|
|
||||||
data class CelestialNames(
|
|
||||||
val systemNames: WeightedList<String> = WeightedList(),
|
|
||||||
val systemPrefixNames: WeightedList<String> = WeightedList(),
|
|
||||||
val systemSuffixNames: WeightedList<String> = WeightedList(),
|
|
||||||
val planetarySuffixes: ImmutableList<String> = ImmutableList.of(),
|
|
||||||
val satelliteSuffixes: ImmutableList<String> = ImmutableList.of(),
|
|
||||||
)
|
|
||||||
|
|
||||||
@JsonFactory
|
|
||||||
data class CelestialBaseInformation(
|
|
||||||
val planetOrbitalLevels: Int = 1,
|
|
||||||
val satelliteOrbitalLevels: Int = 1,
|
|
||||||
val chunkSize: Int = 1,
|
|
||||||
val xyCoordRange: Vector2i = Vector2i.ZERO,
|
|
||||||
val zCoordRange: Vector2i = Vector2i.ZERO,
|
|
||||||
) {
|
|
||||||
constructor(stream: DataInputStream, isLegacy: Boolean) : this(
|
|
||||||
stream.readInt(),
|
|
||||||
stream.readInt(),
|
|
||||||
stream.readInt(),
|
|
||||||
stream.readVector2i(),
|
|
||||||
stream.readVector2i(),
|
|
||||||
)
|
|
||||||
|
|
||||||
fun write(stream: DataOutputStream, isLegacy: Boolean) {
|
|
||||||
stream.writeInt(planetOrbitalLevels)
|
|
||||||
stream.writeInt(satelliteOrbitalLevels)
|
|
||||||
stream.writeInt(chunkSize)
|
|
||||||
stream.writeStruct2i(xyCoordRange)
|
|
||||||
stream.writeStruct2i(zCoordRange)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@JsonFactory
|
|
||||||
data class CelestialOrbitRegion(
|
|
||||||
val regionName: String,
|
|
||||||
val orbitRange: Vector2i,
|
|
||||||
val bodyProbability: Double,
|
|
||||||
val planetaryTypes: WeightedList<String> = WeightedList(),
|
|
||||||
val satelliteTypes: WeightedList<String> = WeightedList(),
|
|
||||||
)
|
|
||||||
|
|
||||||
@JsonFactory
|
|
||||||
data class CelestialPlanet(val parameters: CelestialParameters, val satellites: Int2ObjectMap<CelestialParameters>)
|
|
||||||
|
|
||||||
@JsonFactory
|
@JsonFactory
|
||||||
data class CelestialGenerationInformation(
|
data class CelestialGenerationInformation(
|
||||||
@ -127,7 +74,4 @@ data class CelestialGenerationInformation(
|
|||||||
it.value.typeName = it.key
|
it.value.typeName = it.key
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@JsonFactory
|
|
||||||
data class CelestialParameters(val coordinate: UniversePos, val seed: Long, val name: String, val parameters: JsonObject)
|
|
@ -0,0 +1,14 @@
|
|||||||
|
package ru.dbotthepony.kstarbound.defs.world
|
||||||
|
|
||||||
|
import com.google.common.collect.ImmutableList
|
||||||
|
import ru.dbotthepony.kstarbound.collect.WeightedList
|
||||||
|
import ru.dbotthepony.kstarbound.json.builder.JsonFactory
|
||||||
|
|
||||||
|
@JsonFactory
|
||||||
|
data class CelestialNames(
|
||||||
|
val systemNames: WeightedList<String> = WeightedList(),
|
||||||
|
val systemPrefixNames: WeightedList<String> = WeightedList(),
|
||||||
|
val systemSuffixNames: WeightedList<String> = WeightedList(),
|
||||||
|
val planetarySuffixes: ImmutableList<String> = ImmutableList.of(),
|
||||||
|
val satelliteSuffixes: ImmutableList<String> = ImmutableList.of(),
|
||||||
|
)
|
@ -0,0 +1,14 @@
|
|||||||
|
package ru.dbotthepony.kstarbound.defs.world
|
||||||
|
|
||||||
|
import ru.dbotthepony.kommons.vector.Vector2i
|
||||||
|
import ru.dbotthepony.kstarbound.collect.WeightedList
|
||||||
|
import ru.dbotthepony.kstarbound.json.builder.JsonFactory
|
||||||
|
|
||||||
|
@JsonFactory
|
||||||
|
data class CelestialOrbitRegion(
|
||||||
|
val regionName: String,
|
||||||
|
val orbitRange: Vector2i,
|
||||||
|
val bodyProbability: Double,
|
||||||
|
val planetaryTypes: WeightedList<String> = WeightedList(),
|
||||||
|
val satelliteTypes: WeightedList<String> = WeightedList(),
|
||||||
|
)
|
@ -0,0 +1,82 @@
|
|||||||
|
package ru.dbotthepony.kstarbound.defs.world
|
||||||
|
|
||||||
|
import com.google.gson.Gson
|
||||||
|
import com.google.gson.JsonArray
|
||||||
|
import com.google.gson.JsonObject
|
||||||
|
import com.google.gson.JsonPrimitive
|
||||||
|
import com.google.gson.JsonSyntaxException
|
||||||
|
import com.google.gson.TypeAdapter
|
||||||
|
import com.google.gson.TypeAdapterFactory
|
||||||
|
import com.google.gson.reflect.TypeToken
|
||||||
|
import com.google.gson.stream.JsonReader
|
||||||
|
import com.google.gson.stream.JsonWriter
|
||||||
|
import ru.dbotthepony.kommons.gson.consumeNull
|
||||||
|
import ru.dbotthepony.kommons.gson.value
|
||||||
|
import ru.dbotthepony.kstarbound.json.builder.JsonFactory
|
||||||
|
import ru.dbotthepony.kstarbound.util.random.random
|
||||||
|
import ru.dbotthepony.kstarbound.world.UniversePos
|
||||||
|
|
||||||
|
class CelestialParameters private constructor(val coordinate: UniversePos, val seed: Long, val name: String, val parameters: JsonObject, marker: Nothing?) {
|
||||||
|
constructor(coordinate: UniversePos, seed: Long, name: String, parameters: JsonObject) : this(coordinate, seed, name, parameters, null) {
|
||||||
|
val worldType = parameters["worldType"]?.asString
|
||||||
|
|
||||||
|
if (worldType != null) {
|
||||||
|
visitableParameters = when (worldType.lowercase()) {
|
||||||
|
"terrestrial" -> {
|
||||||
|
val random = random(seed)
|
||||||
|
val worldSize = parameters["worldSize"].asString
|
||||||
|
val type = parameters["terrestrialType"]
|
||||||
|
|
||||||
|
if (type is JsonArray) {
|
||||||
|
TerrestrialWorldParameters.generate(type.random(random).asString, worldSize, random)
|
||||||
|
} else if (type is JsonPrimitive) {
|
||||||
|
TerrestrialWorldParameters.generate(type.asString, worldSize, random)
|
||||||
|
} else {
|
||||||
|
throw JsonSyntaxException("Invalid terrestrialType: $type")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
"asteroids" -> {
|
||||||
|
AsteroidsWorldParameters.generate(seed)
|
||||||
|
}
|
||||||
|
|
||||||
|
"floatingdungeon" -> {
|
||||||
|
FloatingDungeonWorldParameters.generate(parameters["dungeonWorld"].asString)
|
||||||
|
}
|
||||||
|
|
||||||
|
else -> null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor(coordinate: UniversePos, seed: Long, name: String, parameters: JsonObject, visitableParameters: VisitableWorldParameters?) : this(coordinate, seed, name, parameters, null) {
|
||||||
|
this.visitableParameters = visitableParameters
|
||||||
|
}
|
||||||
|
|
||||||
|
var visitableParameters: VisitableWorldParameters? = null
|
||||||
|
private set
|
||||||
|
|
||||||
|
@JsonFactory
|
||||||
|
data class StoreData(val coordinate: UniversePos, val seed: Long, val name: String, val parameters: JsonObject, val visitableParameters: VisitableWorldParameters? = null)
|
||||||
|
|
||||||
|
class Adapter(gson: Gson) : TypeAdapter<CelestialParameters>() {
|
||||||
|
private val data = gson.getAdapter(StoreData::class.java)
|
||||||
|
|
||||||
|
override fun write(out: JsonWriter, value: CelestialParameters?) {
|
||||||
|
if (value == null)
|
||||||
|
out.nullValue()
|
||||||
|
else {
|
||||||
|
data.write(out, StoreData(value.coordinate, value.seed, value.name, value.parameters, value.visitableParameters))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun read(`in`: JsonReader): CelestialParameters? {
|
||||||
|
if (`in`.consumeNull())
|
||||||
|
return null
|
||||||
|
|
||||||
|
val read = data.read(`in`)
|
||||||
|
return CelestialParameters(read.coordinate, read.seed, read.name, read.parameters, read.visitableParameters)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,7 @@
|
|||||||
|
package ru.dbotthepony.kstarbound.defs.world
|
||||||
|
|
||||||
|
import it.unimi.dsi.fastutil.ints.Int2ObjectMap
|
||||||
|
import ru.dbotthepony.kstarbound.json.builder.JsonFactory
|
||||||
|
|
||||||
|
@JsonFactory
|
||||||
|
data class CelestialPlanet(val parameters: CelestialParameters, val satellites: Int2ObjectMap<CelestialParameters>)
|
@ -0,0 +1,44 @@
|
|||||||
|
package ru.dbotthepony.kstarbound.defs.world
|
||||||
|
|
||||||
|
import com.google.common.collect.ImmutableSet
|
||||||
|
import ru.dbotthepony.kommons.math.RGBAColor
|
||||||
|
import ru.dbotthepony.kommons.util.Either
|
||||||
|
import ru.dbotthepony.kommons.vector.Vector2d
|
||||||
|
import ru.dbotthepony.kommons.vector.Vector2i
|
||||||
|
import ru.dbotthepony.kstarbound.collect.WeightedList
|
||||||
|
import ru.dbotthepony.kstarbound.defs.AssetPath
|
||||||
|
import ru.dbotthepony.kstarbound.json.builder.JsonFactory
|
||||||
|
|
||||||
|
@JsonFactory
|
||||||
|
data class DungeonWorldsConfig(
|
||||||
|
val threatLevel: Double,
|
||||||
|
val worldSize: Vector2i,
|
||||||
|
val gravity: Either<Double, Vector2d>,
|
||||||
|
val airless: Boolean = false,
|
||||||
|
val environmentStatusEffects: ImmutableSet<String> = ImmutableSet.of(),
|
||||||
|
val overrideTech: ImmutableSet<String>? = null,
|
||||||
|
val globalDirectives: ImmutableSet<String>? = null,
|
||||||
|
val beamUpRule: BeamUpRule = BeamUpRule.SURFACE, // TODO: ??? why surface? in floating dungeon.
|
||||||
|
val disableDeathDrops: Boolean = false,
|
||||||
|
val worldEdgeForceRegions: WorldEdgeForceRegion = WorldEdgeForceRegion.TOP,
|
||||||
|
val weatherPool: WeightedList<String>? = null,
|
||||||
|
val dungeonBaseHeight: Int,
|
||||||
|
val dungeonSurfaceHeight: Int = dungeonBaseHeight,
|
||||||
|
val dungeonUndergroundLevel: Int = 0,
|
||||||
|
val primaryDungeon: String,
|
||||||
|
val biome: String? = null,
|
||||||
|
val ambientLightLevel: RGBAColor,
|
||||||
|
|
||||||
|
val musicTrack: AssetPath? = null,
|
||||||
|
val dayMusicTrack: AssetPath? = null,
|
||||||
|
val nightMusicTrack: AssetPath? = null,
|
||||||
|
|
||||||
|
val ambientNoises: AssetPath? = null,
|
||||||
|
val dayAmbientNoises: AssetPath? = null,
|
||||||
|
val nightAmbientNoises: AssetPath? = null,
|
||||||
|
) {
|
||||||
|
init {
|
||||||
|
require(worldSize.x > 0) { "Invalid asteroid worlds width: ${worldSize.x}" }
|
||||||
|
require(worldSize.y > 0) { "Invalid asteroid worlds height: ${worldSize.y}" }
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,103 @@
|
|||||||
|
package ru.dbotthepony.kstarbound.defs.world
|
||||||
|
|
||||||
|
import ru.dbotthepony.kommons.math.RGBAColor
|
||||||
|
import ru.dbotthepony.kommons.vector.Vector2d
|
||||||
|
import ru.dbotthepony.kstarbound.GlobalDefaults
|
||||||
|
import ru.dbotthepony.kstarbound.Registries
|
||||||
|
import ru.dbotthepony.kstarbound.util.random.random
|
||||||
|
import kotlin.properties.Delegates
|
||||||
|
|
||||||
|
class FloatingDungeonWorldParameters : VisitableWorldParameters() {
|
||||||
|
var dungeonBaseHeight: Int by Delegates.notNull()
|
||||||
|
private set
|
||||||
|
var dungeonSurfaceHeight: Int by Delegates.notNull()
|
||||||
|
private set
|
||||||
|
var dungeonUndergroundLevel: Int by Delegates.notNull()
|
||||||
|
private set
|
||||||
|
var primaryDungeon: String by Delegates.notNull()
|
||||||
|
private set
|
||||||
|
var ambientLightLevel: RGBAColor by Delegates.notNull()
|
||||||
|
private set
|
||||||
|
var biome: String? = null
|
||||||
|
private set
|
||||||
|
var dayMusicTrack: String? = null
|
||||||
|
private set
|
||||||
|
var nightMusicTrack: String? = null
|
||||||
|
private set
|
||||||
|
var dayAmbientNoises: String? = null
|
||||||
|
private set
|
||||||
|
var nightAmbientNoises: String? = null
|
||||||
|
private set
|
||||||
|
|
||||||
|
override val type: VisitableWorldParametersType
|
||||||
|
get() = VisitableWorldParametersType.FLOATING_DUNGEON
|
||||||
|
|
||||||
|
override fun createLayout(seed: Long): WorldLayout {
|
||||||
|
val random = random(seed)
|
||||||
|
|
||||||
|
val layout = WorldLayout()
|
||||||
|
layout.worldSize = worldSize
|
||||||
|
|
||||||
|
layout.addLayer(random, 0, WorldLayout.RegionParameters(
|
||||||
|
dungeonSurfaceHeight, threatLevel, biome,
|
||||||
|
null, null, null, null, null, null,
|
||||||
|
WorldLayout.RegionLiquids()
|
||||||
|
))
|
||||||
|
|
||||||
|
val color = if (biome != null) {
|
||||||
|
// TODO: Original game engine queries biome database but forgets to finalize world layout if biome is present
|
||||||
|
// TODO: Is that intentional?
|
||||||
|
// (: god damn it.
|
||||||
|
|
||||||
|
Registries.biomes[biome!!]?.value?.skyColoring(random)?.mainColor ?: RGBAColor.BLACK
|
||||||
|
} else {
|
||||||
|
RGBAColor.BLACK
|
||||||
|
}
|
||||||
|
|
||||||
|
layout.finalize(color)
|
||||||
|
return layout
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
fun generate(typeName: String): FloatingDungeonWorldParameters {
|
||||||
|
val config = GlobalDefaults.dungeonWorlds[typeName] ?: throw NoSuchElementException("Unknown dungeon world type $typeName!")
|
||||||
|
val parameters = FloatingDungeonWorldParameters()
|
||||||
|
|
||||||
|
parameters.threatLevel = config.threatLevel
|
||||||
|
parameters.gravity = config.gravity.map({ Vector2d(y = it) }, { it })
|
||||||
|
parameters.airless = config.airless
|
||||||
|
parameters.environmentStatusEffects = config.environmentStatusEffects
|
||||||
|
|
||||||
|
parameters.overrideTech = config.overrideTech
|
||||||
|
parameters.globalDirectives = config.globalDirectives
|
||||||
|
parameters.beamUpRule = config.beamUpRule
|
||||||
|
parameters.disableDeathDrops = config.disableDeathDrops
|
||||||
|
parameters.worldEdgeForceRegions = config.worldEdgeForceRegions
|
||||||
|
parameters.weatherPool = config.weatherPool
|
||||||
|
parameters.dungeonBaseHeight = config.dungeonBaseHeight
|
||||||
|
parameters.dungeonSurfaceHeight = config.dungeonSurfaceHeight
|
||||||
|
parameters.dungeonUndergroundLevel = config.dungeonUndergroundLevel
|
||||||
|
parameters.primaryDungeon = config.primaryDungeon
|
||||||
|
parameters.biome = config.biome
|
||||||
|
parameters.ambientLightLevel = config.ambientLightLevel
|
||||||
|
|
||||||
|
if (config.musicTrack != null) {
|
||||||
|
parameters.dayMusicTrack = config.musicTrack.fullPath
|
||||||
|
parameters.nightMusicTrack = config.musicTrack.fullPath
|
||||||
|
} else {
|
||||||
|
parameters.dayMusicTrack = config.dayMusicTrack?.fullPath
|
||||||
|
parameters.nightMusicTrack = config.nightMusicTrack?.fullPath
|
||||||
|
}
|
||||||
|
|
||||||
|
if (config.ambientNoises != null) {
|
||||||
|
parameters.dayAmbientNoises = config.ambientNoises.fullPath
|
||||||
|
parameters.nightAmbientNoises = config.ambientNoises.fullPath
|
||||||
|
} else {
|
||||||
|
parameters.dayAmbientNoises = config.dayAmbientNoises?.fullPath
|
||||||
|
parameters.nightAmbientNoises = config.nightAmbientNoises?.fullPath
|
||||||
|
}
|
||||||
|
|
||||||
|
return parameters
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,61 @@
|
|||||||
|
package ru.dbotthepony.kstarbound.defs.world
|
||||||
|
|
||||||
|
import com.google.common.collect.ImmutableSet
|
||||||
|
import com.google.gson.JsonElement
|
||||||
|
import ru.dbotthepony.kstarbound.GlobalDefaults
|
||||||
|
import ru.dbotthepony.kstarbound.Registries
|
||||||
|
import ru.dbotthepony.kstarbound.Registry
|
||||||
|
import ru.dbotthepony.kstarbound.defs.AssetReference
|
||||||
|
import ru.dbotthepony.kstarbound.defs.ThingDescription
|
||||||
|
import ru.dbotthepony.kstarbound.defs.tile.TileDamageConfig
|
||||||
|
import ru.dbotthepony.kstarbound.json.builder.JsonFactory
|
||||||
|
import ru.dbotthepony.kstarbound.json.builder.JsonFlat
|
||||||
|
|
||||||
|
@JsonFactory
|
||||||
|
data class GrassVariant(
|
||||||
|
val name: String,
|
||||||
|
val directory: String,
|
||||||
|
val images: ImmutableSet<String> = ImmutableSet.of(),
|
||||||
|
val hueShift: Double,
|
||||||
|
@JsonFlat
|
||||||
|
val descriptions: ThingDescription,
|
||||||
|
val ceiling: Boolean,
|
||||||
|
val ephemeral: Boolean,
|
||||||
|
val tileDamageParameters: TileDamageConfig,
|
||||||
|
) {
|
||||||
|
@JsonFactory
|
||||||
|
data class Data(
|
||||||
|
val name: String,
|
||||||
|
@JsonFlat
|
||||||
|
val descriptions: ThingDescription,
|
||||||
|
val images: ImmutableSet<String>,
|
||||||
|
val ceiling: Boolean = false,
|
||||||
|
val ephemeral: Boolean = true,
|
||||||
|
val description: String = name,
|
||||||
|
val damageTable: AssetReference<TileDamageConfig>? = null,
|
||||||
|
val health: Double = 1.0
|
||||||
|
) {
|
||||||
|
init {
|
||||||
|
require(damageTable == null || damageTable.value != null) { "damageTable must be either absent or point at existing json" }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
fun create(name: String, hueShift: Double): GrassVariant {
|
||||||
|
return create(Registries.grassVariants.getOrThrow(name), hueShift)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun create(data: Registry.Entry<Data>, hueShift: Double): GrassVariant {
|
||||||
|
return GrassVariant(
|
||||||
|
name = data.value.name,
|
||||||
|
directory = data.file?.computeDirectory() ?: "/",
|
||||||
|
images = data.value.images,
|
||||||
|
ceiling = data.value.ceiling,
|
||||||
|
ephemeral = data.value.ephemeral,
|
||||||
|
hueShift = hueShift,
|
||||||
|
descriptions = data.value.descriptions.fixDescription(data.value.name),
|
||||||
|
tileDamageParameters = (data.value.damageTable?.value ?: GlobalDefaults.grassDamage).copy(totalHealth = data.value.health)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
161
src/main/kotlin/ru/dbotthepony/kstarbound/defs/world/Parallax.kt
Normal file
161
src/main/kotlin/ru/dbotthepony/kstarbound/defs/world/Parallax.kt
Normal file
@ -0,0 +1,161 @@
|
|||||||
|
package ru.dbotthepony.kstarbound.defs.world
|
||||||
|
|
||||||
|
import com.google.common.collect.ImmutableList
|
||||||
|
import ru.dbotthepony.kommons.collect.filterNotNull
|
||||||
|
import ru.dbotthepony.kommons.math.RGBAColor
|
||||||
|
import ru.dbotthepony.kommons.util.Either
|
||||||
|
import ru.dbotthepony.kommons.vector.Vector2d
|
||||||
|
import ru.dbotthepony.kstarbound.defs.image.Image
|
||||||
|
import ru.dbotthepony.kstarbound.json.builder.JsonFactory
|
||||||
|
import ru.dbotthepony.kstarbound.util.RenderDirectives
|
||||||
|
import ru.dbotthepony.kstarbound.util.random.nextRange
|
||||||
|
import java.util.random.RandomGenerator
|
||||||
|
|
||||||
|
@JsonFactory
|
||||||
|
class Parallax(
|
||||||
|
// ah yes storing useless data once again
|
||||||
|
val seed: Long = 0L,
|
||||||
|
val verticalOrigin: Double,
|
||||||
|
val hueShift: Double,
|
||||||
|
val imageDirectory: String,
|
||||||
|
val parallaxTreeVariant: TreeVariant?,
|
||||||
|
val layers: ImmutableList<Layer> = ImmutableList.of(),
|
||||||
|
) {
|
||||||
|
@JsonFactory
|
||||||
|
data class Data(
|
||||||
|
val verticalOrigin: Double = 0.0,
|
||||||
|
val layers: ImmutableList<DataLayer>,
|
||||||
|
) {
|
||||||
|
fun create(random: RandomGenerator, verticalOrigin: Double, hueShift: Double, treeVariant: TreeVariant?): Parallax {
|
||||||
|
return Parallax(
|
||||||
|
seed = random.nextLong(),
|
||||||
|
verticalOrigin = verticalOrigin + this.verticalOrigin,
|
||||||
|
hueShift = hueShift,
|
||||||
|
imageDirectory = "/parallax/images/",
|
||||||
|
parallaxTreeVariant = treeVariant,
|
||||||
|
layers = layers.stream()
|
||||||
|
.filter { it.frequency >= random.nextDouble() }
|
||||||
|
.map { it.create(random, treeVariant, verticalOrigin + this.verticalOrigin, hueShift) }
|
||||||
|
.filterNotNull()
|
||||||
|
.collect(ImmutableList.toImmutableList())
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun fadeToSkyColor(color: RGBAColor) {
|
||||||
|
layers.forEach { it.fadeToSkyColor(color) }
|
||||||
|
}
|
||||||
|
|
||||||
|
@JsonFactory
|
||||||
|
data class DataLayer(
|
||||||
|
val kind: String,
|
||||||
|
val frequency: Double = 1.0,
|
||||||
|
val baseCount: Int = 1,
|
||||||
|
val modCount: Int = 0,
|
||||||
|
val parallax: Either<Double, Vector2d>,
|
||||||
|
val repeatY: Boolean = true,
|
||||||
|
val repeatX: Boolean = true,
|
||||||
|
val tileLimitTop: Double? = null,
|
||||||
|
val tileLimitBottom: Double? = null,
|
||||||
|
val offset: Vector2d = Vector2d.ZERO, // shift from bottom left to horizon level in the image
|
||||||
|
val noRandomOffset: Boolean = false,
|
||||||
|
val timeOfDayCorrelation: String = "",
|
||||||
|
val minSpeed: Double = 0.0,
|
||||||
|
val maxSpeed: Double = 0.0,
|
||||||
|
val unlit: Boolean = false,
|
||||||
|
val lightMapped: Boolean = false,
|
||||||
|
val directives: String = "",
|
||||||
|
val fadePercent: Double = 0.0,
|
||||||
|
val nohueshift: Boolean = false,
|
||||||
|
) {
|
||||||
|
init {
|
||||||
|
require(baseCount >= 1) { "non positive baseCount: $baseCount" }
|
||||||
|
}
|
||||||
|
|
||||||
|
fun create(random: RandomGenerator, treeVariant: TreeVariant?, verticalOrigin: Double, hueShift: Double): Layer? {
|
||||||
|
val isFoliage = kind.startsWith("foliage/")
|
||||||
|
val isStem = kind.startsWith("stem/")
|
||||||
|
|
||||||
|
var texPath = "/parallax/images/$kind/"
|
||||||
|
|
||||||
|
if (isFoliage) {
|
||||||
|
if (treeVariant == null) return null
|
||||||
|
if (!treeVariant.foliageSettings.parallaxFoliage) return null
|
||||||
|
texPath = "${treeVariant.foliageDirectory}/parallax/${kind.replace("foliage/", "")}/"
|
||||||
|
} else if (isStem) {
|
||||||
|
if (treeVariant == null) return null
|
||||||
|
texPath = "${treeVariant.stemDirectory}/parallax/${kind.replace("stem/", "")}/"
|
||||||
|
}
|
||||||
|
|
||||||
|
val textures = ArrayList<String>()
|
||||||
|
val base = if (baseCount <= 1) 1 else random.nextInt(baseCount - 1) + 1
|
||||||
|
textures.add("${texPath}base/$base.png")
|
||||||
|
|
||||||
|
val modCount = if (modCount == 0) 0 else random.nextInt(modCount) + 1
|
||||||
|
if (modCount != 0)
|
||||||
|
textures.add("${texPath}mod/${modCount + 1}.png")
|
||||||
|
|
||||||
|
var directives = directives
|
||||||
|
|
||||||
|
if (isFoliage)
|
||||||
|
directives += "?hueshift=${treeVariant!!.foliageHueShift}"
|
||||||
|
else if (isStem)
|
||||||
|
directives += "?hueshift=${treeVariant!!.stemHueShift}"
|
||||||
|
else if (!nohueshift)
|
||||||
|
directives += "?hueshift=$hueShift"
|
||||||
|
|
||||||
|
var offset = offset
|
||||||
|
|
||||||
|
if (!noRandomOffset) {
|
||||||
|
val width = Image.get(textures.first())?.width ?: 0
|
||||||
|
|
||||||
|
if (width > 0) {
|
||||||
|
offset += Vector2d(x = random.nextInt(width).toDouble())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return Layer(
|
||||||
|
parallaxValue = parallax.map({ Vector2d(it, it) }, { it }),
|
||||||
|
repeat = repeatX to repeatY,
|
||||||
|
tileLimitTop = tileLimitTop,
|
||||||
|
tileLimitBottom = tileLimitBottom,
|
||||||
|
verticalOrigin = verticalOrigin,
|
||||||
|
zLevel = parallax.map({ it * 2.0 }, { it.x + it.y }),
|
||||||
|
parallaxOffset = offset,
|
||||||
|
timeOfDayCorrelation = timeOfDayCorrelation,
|
||||||
|
speed = random.nextRange(Vector2d(minSpeed, maxSpeed)),
|
||||||
|
unlit = unlit,
|
||||||
|
lightMapped = lightMapped,
|
||||||
|
fadePercent = fadePercent,
|
||||||
|
alpha = 1.0,
|
||||||
|
directives = RenderDirectives(directives),
|
||||||
|
textures = ImmutableList.copyOf(textures),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@JsonFactory
|
||||||
|
data class Layer(
|
||||||
|
var directives: RenderDirectives,
|
||||||
|
val textures: ImmutableList<String>,
|
||||||
|
val alpha: Double,
|
||||||
|
val parallaxValue: Vector2d,
|
||||||
|
val repeat: Pair<Boolean, Boolean>,
|
||||||
|
val tileLimitTop: Double? = null,
|
||||||
|
val tileLimitBottom: Double? = null,
|
||||||
|
val verticalOrigin: Double,
|
||||||
|
val zLevel: Double,
|
||||||
|
val parallaxOffset: Vector2d,
|
||||||
|
val timeOfDayCorrelation: String,
|
||||||
|
val speed: Double,
|
||||||
|
val unlit: Boolean,
|
||||||
|
val lightMapped: Boolean,
|
||||||
|
val fadePercent: Double,
|
||||||
|
) {
|
||||||
|
fun fadeToSkyColor(color: RGBAColor) {
|
||||||
|
if (fadePercent > 0.0) {
|
||||||
|
directives = directives.add("fade", color.toHexStringRGB() + "=$fadePercent")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
165
src/main/kotlin/ru/dbotthepony/kstarbound/defs/world/Sky.kt
Normal file
165
src/main/kotlin/ru/dbotthepony/kstarbound/defs/world/Sky.kt
Normal file
@ -0,0 +1,165 @@
|
|||||||
|
package ru.dbotthepony.kstarbound.defs.world
|
||||||
|
|
||||||
|
import com.google.gson.stream.JsonWriter
|
||||||
|
import ru.dbotthepony.kommons.math.RGBAColor
|
||||||
|
import ru.dbotthepony.kommons.util.Either
|
||||||
|
import ru.dbotthepony.kommons.vector.Vector2d
|
||||||
|
import ru.dbotthepony.kstarbound.io.readColor
|
||||||
|
import ru.dbotthepony.kstarbound.io.writeColor
|
||||||
|
import ru.dbotthepony.kstarbound.json.builder.IStringSerializable
|
||||||
|
import ru.dbotthepony.kstarbound.json.builder.JsonFactory
|
||||||
|
import ru.dbotthepony.kstarbound.util.random.random
|
||||||
|
import ru.dbotthepony.kstarbound.world.Universe
|
||||||
|
import ru.dbotthepony.kstarbound.world.UniversePos
|
||||||
|
import java.io.DataInputStream
|
||||||
|
import java.io.DataOutputStream
|
||||||
|
|
||||||
|
enum class SkyType {
|
||||||
|
BARREN,
|
||||||
|
ATMOSPHERIC,
|
||||||
|
ATMOSPHERELESS,
|
||||||
|
ORBITAL,
|
||||||
|
WARP,
|
||||||
|
SPACE
|
||||||
|
}
|
||||||
|
|
||||||
|
enum class FlyingType {
|
||||||
|
NONE,
|
||||||
|
DISEMBARKING,
|
||||||
|
WARP,
|
||||||
|
ARRIVING
|
||||||
|
}
|
||||||
|
|
||||||
|
enum class WarpPhase(val stupidassbitch: Int) {
|
||||||
|
SLOWING_DOWN(-1),
|
||||||
|
MAINTAIN(0),
|
||||||
|
SPEEDING_UP(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
enum class SkyOrbiterType(val sname: String) : IStringSerializable {
|
||||||
|
SUN("sun"),
|
||||||
|
MOON("moon"),
|
||||||
|
HORIZON_CLOUD("horizoncloud"),
|
||||||
|
SPACE_DEBRIS("scapedebris");
|
||||||
|
|
||||||
|
override fun match(name: String): Boolean {
|
||||||
|
return name.lowercase() == sname
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun write(out: JsonWriter) {
|
||||||
|
out.value(sname)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@JsonFactory
|
||||||
|
data class SkyOrbiter(val type: SkyOrbiterType, val scale: Double, val angle: Double, val image: String, val position: Vector2d)
|
||||||
|
|
||||||
|
@JsonFactory
|
||||||
|
data class SkyColoring(
|
||||||
|
val mainColor: RGBAColor = RGBAColor.BLACK,
|
||||||
|
|
||||||
|
val morningColors: Pair<RGBAColor, RGBAColor> = RGBAColor.BLACK to RGBAColor.BLACK,
|
||||||
|
val dayColors: Pair<RGBAColor, RGBAColor> = RGBAColor.BLACK to RGBAColor.BLACK,
|
||||||
|
val eveningColors: Pair<RGBAColor, RGBAColor> = RGBAColor.BLACK to RGBAColor.BLACK,
|
||||||
|
val nightColors: Pair<RGBAColor, RGBAColor> = RGBAColor.BLACK to RGBAColor.BLACK,
|
||||||
|
|
||||||
|
val morningLightColor: RGBAColor = RGBAColor.BLACK,
|
||||||
|
val dayLightColor: RGBAColor = RGBAColor.BLACK,
|
||||||
|
val eveningLightColor: RGBAColor = RGBAColor.BLACK,
|
||||||
|
val nightLightColor: RGBAColor = RGBAColor.BLACK,
|
||||||
|
) {
|
||||||
|
fun write(stream: DataOutputStream, isLegacy: Boolean) {
|
||||||
|
stream.writeColor(mainColor)
|
||||||
|
|
||||||
|
stream.writeColor(morningColors.first)
|
||||||
|
stream.writeColor(morningColors.second)
|
||||||
|
stream.writeColor(dayColors.first)
|
||||||
|
stream.writeColor(dayColors.second)
|
||||||
|
stream.writeColor(eveningColors.first)
|
||||||
|
stream.writeColor(eveningColors.second)
|
||||||
|
stream.writeColor(nightColors.first)
|
||||||
|
stream.writeColor(nightColors.second)
|
||||||
|
|
||||||
|
stream.writeColor(morningLightColor)
|
||||||
|
stream.writeColor(dayLightColor)
|
||||||
|
stream.writeColor(eveningLightColor)
|
||||||
|
stream.writeColor(nightLightColor)
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
val BLACK = SkyColoring()
|
||||||
|
|
||||||
|
fun read(stream: DataInputStream, isLegacy: Boolean): SkyColoring {
|
||||||
|
val mainColor = stream.readColor()
|
||||||
|
|
||||||
|
val morningColors = stream.readColor() to stream.readColor()
|
||||||
|
val dayColors = stream.readColor() to stream.readColor()
|
||||||
|
val eveningColors = stream.readColor() to stream.readColor()
|
||||||
|
val nightColors = stream.readColor() to stream.readColor()
|
||||||
|
|
||||||
|
val morningLightColor = stream.readColor()
|
||||||
|
val dayLightColor = stream.readColor()
|
||||||
|
val eveningLightColor = stream.readColor()
|
||||||
|
val nightLightColor = stream.readColor()
|
||||||
|
|
||||||
|
return SkyColoring(
|
||||||
|
mainColor = mainColor,
|
||||||
|
morningColors = morningColors,
|
||||||
|
dayColors = dayColors,
|
||||||
|
eveningColors = eveningColors,
|
||||||
|
nightColors = nightColors,
|
||||||
|
morningLightColor = morningLightColor,
|
||||||
|
dayLightColor = dayLightColor,
|
||||||
|
eveningLightColor = eveningLightColor,
|
||||||
|
nightLightColor = nightLightColor,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
data class SkyWorldHorizon(val center: Vector2d, val scale: Double, val rotation: Double, val layers: List<Pair<String, String>>)
|
||||||
|
|
||||||
|
class SkyParameters() {
|
||||||
|
var skyType = SkyType.BARREN
|
||||||
|
var seed = 0L
|
||||||
|
var dayLength: Double? = null
|
||||||
|
var horizonClouds = false
|
||||||
|
var skyColoring: Either<SkyColoring, RGBAColor> = Either.right(RGBAColor.BLACK)
|
||||||
|
var spaceLevel: Double? = null
|
||||||
|
var surfaceLevel: Double? = null
|
||||||
|
var nearbyPlanet: Pair<List<Pair<String, Double>>, Vector2d>? = null
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
suspend fun create(coordinate: UniversePos, universe: Universe): SkyParameters {
|
||||||
|
if (coordinate.isSystem)
|
||||||
|
throw IllegalArgumentException("$coordinate is system location")
|
||||||
|
|
||||||
|
val params = universe.parameters(coordinate) ?: throw IllegalArgumentException("$universe has nothing at $coordinate!")
|
||||||
|
|
||||||
|
val random = random(params.seed)
|
||||||
|
val selfPos = params.coordinate
|
||||||
|
|
||||||
|
val sky = SkyParameters()
|
||||||
|
|
||||||
|
if (selfPos.isSatellite) {
|
||||||
|
val planet = universe.parameters(selfPos.parent())
|
||||||
|
|
||||||
|
if (planet != null) {
|
||||||
|
val pos = Vector2d(random.nextDouble(), random.nextDouble())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (satellitePos in universe.children(selfPos.planet())) {
|
||||||
|
if (satellitePos != selfPos) {
|
||||||
|
val satellite = universe.parameters(satellitePos)
|
||||||
|
|
||||||
|
if (satellite != null) {
|
||||||
|
val pos = Vector2d(random.nextDouble(), random.nextDouble())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return sky
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,484 @@
|
|||||||
|
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
|
||||||
|
import com.google.gson.reflect.TypeToken
|
||||||
|
import it.unimi.dsi.fastutil.ints.IntArrayList
|
||||||
|
import ru.dbotthepony.kommons.gson.contains
|
||||||
|
import ru.dbotthepony.kommons.gson.get
|
||||||
|
import ru.dbotthepony.kommons.gson.getArray
|
||||||
|
import ru.dbotthepony.kommons.gson.getObject
|
||||||
|
import ru.dbotthepony.kommons.gson.set
|
||||||
|
import ru.dbotthepony.kommons.util.Either
|
||||||
|
import ru.dbotthepony.kommons.vector.Vector2d
|
||||||
|
import ru.dbotthepony.kommons.vector.Vector2i
|
||||||
|
import ru.dbotthepony.kstarbound.GlobalDefaults
|
||||||
|
import ru.dbotthepony.kstarbound.Registries
|
||||||
|
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
|
||||||
|
import kotlin.properties.Delegates
|
||||||
|
|
||||||
|
class TerrestrialWorldParameters : VisitableWorldParameters() {
|
||||||
|
@JsonFactory
|
||||||
|
data class Generic(
|
||||||
|
// TODO: while engine supports arbitrary gravity direction,
|
||||||
|
// TODO: json files don't have the same support level
|
||||||
|
val gravityRange: Vector2d,
|
||||||
|
val dayLengthRange: Vector2d,
|
||||||
|
val threatRange: Vector2d = Vector2d(1.0, 1.0),
|
||||||
|
val size: Vector2i,
|
||||||
|
val overrideTech: ImmutableSet<String>? = null,
|
||||||
|
val globalDirectives: ImmutableSet<String>? = null,
|
||||||
|
val disableDeathDrops: Boolean = false,
|
||||||
|
val beamUpRule: BeamUpRule = BeamUpRule.SURFACE,
|
||||||
|
val worldEdgeForceRegions: WorldEdgeForceRegion = WorldEdgeForceRegion.TOP,
|
||||||
|
|
||||||
|
val blockNoise: BlockNoiseConfig? = null,
|
||||||
|
val blendNoise: PerlinNoiseParameters? = null,
|
||||||
|
val blendSize: Double,
|
||||||
|
)
|
||||||
|
|
||||||
|
@JsonFactory
|
||||||
|
data class Region(
|
||||||
|
val biome: String,
|
||||||
|
|
||||||
|
val blockSelector: String,
|
||||||
|
val fgCaveSelector: String,
|
||||||
|
val bgCaveSelector: String,
|
||||||
|
val fgOreSelector: String,
|
||||||
|
val bgOreSelector: String,
|
||||||
|
val subBlockSelector: String,
|
||||||
|
|
||||||
|
val caveLiquid: Either<Int, String>?,
|
||||||
|
val caveLiquidSeedDensity: Double,
|
||||||
|
val oceanLiquid: Either<Int, String>?,
|
||||||
|
val oceanLiquidLevel: Int,
|
||||||
|
|
||||||
|
val encloseLiquids: Boolean,
|
||||||
|
val fillMicrodungeons: Boolean,
|
||||||
|
)
|
||||||
|
|
||||||
|
@JsonFactory
|
||||||
|
data class Layer(
|
||||||
|
val layerMinHeight: Int,
|
||||||
|
val layerBaseHeight: Int,
|
||||||
|
|
||||||
|
val dungeons: ImmutableSet<String>,
|
||||||
|
val dungeonXVariance: Int,
|
||||||
|
|
||||||
|
val primaryRegion: Region,
|
||||||
|
val primarySubRegion: Region,
|
||||||
|
|
||||||
|
val secondaryRegions: ImmutableList<Region>,
|
||||||
|
val secondarySubRegions: ImmutableList<Region>,
|
||||||
|
|
||||||
|
val secondaryRegionSizeRange: Vector2d,
|
||||||
|
val subRegionSizeRange: Vector2d,
|
||||||
|
)
|
||||||
|
|
||||||
|
override fun fromJson(data: JsonObject) {
|
||||||
|
super.fromJson(data)
|
||||||
|
|
||||||
|
val read = Starbound.gson.fromJson(data, StoreData::class.java)
|
||||||
|
|
||||||
|
primaryBiome = read.primaryBiome
|
||||||
|
primarySurfaceLiquid = read.primarySurfaceLiquid
|
||||||
|
sizeName = read.sizeName
|
||||||
|
hueShift = read.hueShift
|
||||||
|
skyColoring = read.skyColoring
|
||||||
|
dayLength = read.dayLength
|
||||||
|
blockNoiseConfig = read.blockNoiseConfig
|
||||||
|
blendNoiseConfig = read.blendNoiseConfig
|
||||||
|
blendSize = read.blendSize
|
||||||
|
spaceLayer = read.spaceLayer
|
||||||
|
atmosphereLayer = read.atmosphereLayer
|
||||||
|
surfaceLayer = read.surfaceLayer
|
||||||
|
subsurfaceLayer = read.subsurfaceLayer
|
||||||
|
undergroundLayers = read.undergroundLayers
|
||||||
|
coreLayer = read.coreLayer
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun toJson(data: JsonObject, isLegacy: Boolean) {
|
||||||
|
super.toJson(data, isLegacy)
|
||||||
|
|
||||||
|
val primarySurfaceLiquid: Either<Int, String>?
|
||||||
|
|
||||||
|
// 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) {
|
||||||
|
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) }
|
||||||
|
} else {
|
||||||
|
primarySurfaceLiquid = this.primarySurfaceLiquid
|
||||||
|
}
|
||||||
|
|
||||||
|
val store = StoreData(
|
||||||
|
primaryBiome = primaryBiome,
|
||||||
|
primarySurfaceLiquid = primarySurfaceLiquid,
|
||||||
|
sizeName = sizeName,
|
||||||
|
hueShift = hueShift,
|
||||||
|
skyColoring = skyColoring,
|
||||||
|
dayLength = dayLength,
|
||||||
|
blockNoiseConfig = blockNoiseConfig,
|
||||||
|
blendNoiseConfig = blendNoiseConfig,
|
||||||
|
blendSize = blendSize,
|
||||||
|
spaceLayer = spaceLayer,
|
||||||
|
atmosphereLayer = atmosphereLayer,
|
||||||
|
surfaceLayer = surfaceLayer,
|
||||||
|
subsurfaceLayer = subsurfaceLayer,
|
||||||
|
undergroundLayers = undergroundLayers,
|
||||||
|
coreLayer = coreLayer,
|
||||||
|
)
|
||||||
|
|
||||||
|
for ((k, v) in (Starbound.gson.toJsonTree(store) as JsonObject).entrySet()) {
|
||||||
|
data[k] = v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@JsonFactory
|
||||||
|
data class StoreData(
|
||||||
|
val primaryBiome: String,
|
||||||
|
val primarySurfaceLiquid: Either<Int, String>?,
|
||||||
|
val sizeName: String,
|
||||||
|
val hueShift: Double,
|
||||||
|
val skyColoring: SkyColoring,
|
||||||
|
val dayLength: Double,
|
||||||
|
val blockNoiseConfig: BlockNoiseConfig? = null,
|
||||||
|
val blendNoiseConfig: PerlinNoiseParameters? = null,
|
||||||
|
val blendSize: Double,
|
||||||
|
val spaceLayer: Layer,
|
||||||
|
val atmosphereLayer: Layer,
|
||||||
|
val surfaceLayer: Layer,
|
||||||
|
val subsurfaceLayer: Layer,
|
||||||
|
val undergroundLayers: List<Layer>,
|
||||||
|
val coreLayer: Layer,
|
||||||
|
)
|
||||||
|
|
||||||
|
var primaryBiome: String by Delegates.notNull()
|
||||||
|
private set
|
||||||
|
var primarySurfaceLiquid: Either<Int, String>? = null
|
||||||
|
private set
|
||||||
|
var sizeName: String by Delegates.notNull()
|
||||||
|
private set
|
||||||
|
var hueShift: Double by Delegates.notNull()
|
||||||
|
private set
|
||||||
|
var skyColoring: SkyColoring by Delegates.notNull()
|
||||||
|
private set
|
||||||
|
var dayLength: Double by Delegates.notNull()
|
||||||
|
private set
|
||||||
|
var blockNoiseConfig: BlockNoiseConfig? = null
|
||||||
|
private set
|
||||||
|
var blendNoiseConfig: PerlinNoiseParameters? = null
|
||||||
|
private set
|
||||||
|
var blendSize: Double by Delegates.notNull()
|
||||||
|
private set
|
||||||
|
|
||||||
|
var spaceLayer: Layer by Delegates.notNull()
|
||||||
|
private set
|
||||||
|
var atmosphereLayer: Layer by Delegates.notNull()
|
||||||
|
private set
|
||||||
|
var surfaceLayer: Layer by Delegates.notNull()
|
||||||
|
private set
|
||||||
|
var subsurfaceLayer: Layer by Delegates.notNull()
|
||||||
|
private set
|
||||||
|
var undergroundLayers: List<Layer> by Delegates.notNull()
|
||||||
|
private set
|
||||||
|
var coreLayer: Layer by Delegates.notNull()
|
||||||
|
private set
|
||||||
|
|
||||||
|
override val type: VisitableWorldParametersType
|
||||||
|
get() = VisitableWorldParametersType.TERRESTRIAL
|
||||||
|
|
||||||
|
// why
|
||||||
|
override fun createLayout(seed: Long): WorldLayout {
|
||||||
|
val layout = WorldLayout()
|
||||||
|
layout.worldSize = worldSize
|
||||||
|
|
||||||
|
val random = random(seed)
|
||||||
|
|
||||||
|
fun addLayer(layer: Layer) {
|
||||||
|
fun bake(region: Region): WorldLayout.RegionParameters {
|
||||||
|
return WorldLayout.RegionParameters(
|
||||||
|
layer.layerBaseHeight,
|
||||||
|
threatLevel,
|
||||||
|
region.biome,
|
||||||
|
region.blockSelector,
|
||||||
|
region.fgCaveSelector,
|
||||||
|
region.bgCaveSelector,
|
||||||
|
region.fgOreSelector,
|
||||||
|
region.bgOreSelector,
|
||||||
|
region.subBlockSelector,
|
||||||
|
WorldLayout.RegionLiquids(
|
||||||
|
region.caveLiquid,
|
||||||
|
region.caveLiquidSeedDensity,
|
||||||
|
region.oceanLiquid,
|
||||||
|
region.oceanLiquidLevel,
|
||||||
|
region.encloseLiquids,
|
||||||
|
region.fillMicrodungeons,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
val primaryRegionParams = bake(layer.primaryRegion)
|
||||||
|
val primarySubRegionParams = bake(layer.primarySubRegion)
|
||||||
|
val secondary = layer.secondaryRegions.map { bake(it) }
|
||||||
|
val secondarySubRegions = layer.secondarySubRegions.map { bake(it) }
|
||||||
|
|
||||||
|
layout.addLayer(
|
||||||
|
random, layer.layerMinHeight, layer.layerBaseHeight,
|
||||||
|
primaryBiome,
|
||||||
|
primaryRegionParams, primarySubRegionParams,
|
||||||
|
secondary, secondarySubRegions,
|
||||||
|
layer.secondaryRegionSizeRange, layer.subRegionSizeRange)
|
||||||
|
}
|
||||||
|
|
||||||
|
addLayer(coreLayer)
|
||||||
|
undergroundLayers.asReversed().forEach { addLayer(it) }
|
||||||
|
addLayer(subsurfaceLayer)
|
||||||
|
addLayer(surfaceLayer)
|
||||||
|
addLayer(atmosphereLayer)
|
||||||
|
addLayer(spaceLayer)
|
||||||
|
|
||||||
|
layout.regionBlending = blendSize
|
||||||
|
layout.blockNoise = blockNoiseConfig?.build(random)
|
||||||
|
layout.blendNoise = blendNoiseConfig?.let { AbstractPerlinNoise.of(it).also { it.init(seed) } }
|
||||||
|
|
||||||
|
layout.finalize(skyColoring.mainColor)
|
||||||
|
|
||||||
|
return layout
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private val biomePairs by lazy { Starbound.gson.pairAdapter<Double, JsonArray>() }
|
||||||
|
private val vectors2d by lazy { Starbound.gson.getAdapter(Vector2d::class.java) }
|
||||||
|
private val vectors2i by lazy { Starbound.gson.getAdapter(Vector2i::class.java) }
|
||||||
|
private val dungeonPools by lazy { Starbound.gson.getAdapter(TypeToken.getParameterized(WeightedList::class.java, String::class.java)) as TypeAdapter<WeightedList<String>> }
|
||||||
|
|
||||||
|
fun generate(typeName: String, sizeName: String, seed: Long): TerrestrialWorldParameters {
|
||||||
|
return generate(typeName, sizeName, random(seed))
|
||||||
|
}
|
||||||
|
|
||||||
|
fun generate(typeName: String, sizeName: String, random: RandomGenerator): TerrestrialWorldParameters {
|
||||||
|
val config = GlobalDefaults.terrestrialWorlds.planetDefaults.deepCopy()
|
||||||
|
JsonDriven.mergeNoCopy(config, GlobalDefaults.terrestrialWorlds.planetSizes[sizeName] ?: throw NoSuchElementException("Unknown world size name $sizeName"))
|
||||||
|
JsonDriven.mergeNoCopy(config, GlobalDefaults.terrestrialWorlds.planetTypes[typeName] ?: throw NoSuchElementException("Unknown world type name $typeName"))
|
||||||
|
|
||||||
|
val params = Starbound.gson.fromJson(config, Generic::class.java)
|
||||||
|
|
||||||
|
val threadLevel = random.nextRange(params.threatRange)
|
||||||
|
val layers = config.getObject("layers")
|
||||||
|
|
||||||
|
fun readRegion(regionConfig: JsonObject, layerBaseHeight: Int): Region {
|
||||||
|
val biome = regionConfig["biome"]
|
||||||
|
.asJsonArray
|
||||||
|
.stream()
|
||||||
|
.map { biomePairs.fromJsonTree(it) }
|
||||||
|
.binnedChoice(threadLevel)
|
||||||
|
.map { it.stream().map { it.asString }.toList() }
|
||||||
|
.orElse(listOf())
|
||||||
|
.random(random)
|
||||||
|
|
||||||
|
val blockSelector = regionConfig.getArray("blockSelector").random(random).asString
|
||||||
|
val fgCaveSelector = regionConfig.getArray("fgCaveSelector").random(random).asString
|
||||||
|
val bgCaveSelector = regionConfig.getArray("bgCaveSelector").random(random).asString
|
||||||
|
val fgOreSelector = regionConfig.getArray("fgOreSelector").random(random).asString
|
||||||
|
val bgOreSelector = regionConfig.getArray("bgOreSelector").random(random).asString
|
||||||
|
val subBlockSelector = regionConfig.getArray("subBlockSelector").random(random).asString
|
||||||
|
|
||||||
|
// original engine here translates liquid name into liquid id
|
||||||
|
// we, however, do not, since we prefer to operate on registry names and not registry ids
|
||||||
|
val caveLiquid: String?
|
||||||
|
val caveLiquidSeedDensity: Double
|
||||||
|
val oceanLiquid: String?
|
||||||
|
val oceanLiquidLevel: Int
|
||||||
|
|
||||||
|
// "can be empty for no liquid"
|
||||||
|
// yeah thanks fucking fuck
|
||||||
|
// I hope whoever designed original loading code will have
|
||||||
|
// their software blown up by C++'s "feature" to implicitly construct classes/structs/values
|
||||||
|
// out of thin air
|
||||||
|
if ("caveLiquid" in regionConfig) {
|
||||||
|
caveLiquid = regionConfig.getArray("caveLiquid").random(random) { JsonPrimitive("") }.asString.let { if (it.isBlank()) null else it }
|
||||||
|
val range = Starbound.gson.fromJson(regionConfig["caveLiquidSeedDensityRange"], Vector2d::class.java)
|
||||||
|
caveLiquidSeedDensity = if (range.x == range.y) range.x else random.nextDouble(range.x, range.y)
|
||||||
|
} else {
|
||||||
|
caveLiquid = null
|
||||||
|
caveLiquidSeedDensity = 0.0
|
||||||
|
}
|
||||||
|
|
||||||
|
if ("oceanLiquid" in regionConfig) {
|
||||||
|
oceanLiquid = regionConfig.getArray("oceanLiquid").random(random) { JsonPrimitive("") }.asString.let { if (it.isBlank()) null else it }
|
||||||
|
oceanLiquidLevel = regionConfig.get("oceanLevelOffset", 0) + layerBaseHeight
|
||||||
|
} else {
|
||||||
|
oceanLiquid = null
|
||||||
|
oceanLiquidLevel = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
val encloseLiquids = regionConfig.get("encloseLiquids", false)
|
||||||
|
val fillMicrodungeons = regionConfig.get("fillMicrodungeons", false)
|
||||||
|
|
||||||
|
return Region(
|
||||||
|
biome = biome,
|
||||||
|
blockSelector = blockSelector,
|
||||||
|
fgCaveSelector = fgCaveSelector,
|
||||||
|
bgCaveSelector = bgCaveSelector,
|
||||||
|
fgOreSelector = fgOreSelector,
|
||||||
|
bgOreSelector = bgOreSelector,
|
||||||
|
subBlockSelector = subBlockSelector,
|
||||||
|
caveLiquid = caveLiquid?.let { Either.right(it) },
|
||||||
|
caveLiquidSeedDensity = caveLiquidSeedDensity,
|
||||||
|
oceanLiquid = oceanLiquid?.let { Either.right(it) },
|
||||||
|
oceanLiquidLevel = oceanLiquidLevel,
|
||||||
|
encloseLiquids = encloseLiquids,
|
||||||
|
fillMicrodungeons = fillMicrodungeons,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun makeRegion(name: String, baseHeight: Int): Pair<Region, Region> {
|
||||||
|
val primaryRegionJson = GlobalDefaults.terrestrialWorlds.regionDefaults.deepCopy()
|
||||||
|
JsonDriven.mergeNoCopy(primaryRegionJson, GlobalDefaults.terrestrialWorlds.regionTypes[name]!!)
|
||||||
|
|
||||||
|
val region = readRegion(primaryRegionJson, baseHeight)
|
||||||
|
val subRegionList = primaryRegionJson.getArray("subRegion")
|
||||||
|
|
||||||
|
val subRegion = readRegion(if (subRegionList.isEmpty) {
|
||||||
|
primaryRegionJson
|
||||||
|
} else {
|
||||||
|
val result = GlobalDefaults.terrestrialWorlds.regionDefaults.deepCopy()
|
||||||
|
JsonDriven.mergeNoCopy(result, GlobalDefaults.terrestrialWorlds.regionTypes[subRegionList.random(random).asString]!!)
|
||||||
|
result
|
||||||
|
}, baseHeight)
|
||||||
|
|
||||||
|
return region to subRegion
|
||||||
|
}
|
||||||
|
|
||||||
|
fun readLayer(layerName: String): Layer? {
|
||||||
|
if (layerName !in layers)
|
||||||
|
return null
|
||||||
|
|
||||||
|
val layerConfig = config.getObject("layerDefaults").deepCopy()
|
||||||
|
JsonDriven.mergeNoCopy(layerConfig, layers.getObject(layerName))
|
||||||
|
|
||||||
|
if (!layerConfig.get("enabled", false))
|
||||||
|
return null
|
||||||
|
|
||||||
|
val layerLevel = layerConfig["layerLevel"].asInt
|
||||||
|
val baseHeight = layerConfig["baseHeight"].asInt
|
||||||
|
|
||||||
|
val (primary, subPrimary) = makeRegion(layerConfig.getArray("primaryRegion").random(random).asString, baseHeight)
|
||||||
|
|
||||||
|
val secondaryRegionCountRange = vectors2i.fromJsonTree(layerConfig["secondaryRegionCount"])
|
||||||
|
var secondaryRegionCount = random.nextRange(secondaryRegionCountRange)
|
||||||
|
|
||||||
|
val secondaryRegionList = layerConfig.getArray("secondaryRegions")
|
||||||
|
val secondary = ArrayList<Pair<Region, Region>>()
|
||||||
|
|
||||||
|
if (!secondaryRegionList.isEmpty && secondaryRegionCount > 0) {
|
||||||
|
val indices = IntArrayList(secondaryRegionList.size())
|
||||||
|
indices.addAll(0 until secondaryRegionList.size())
|
||||||
|
val shuffled = IntArrayList(secondaryRegionList.size())
|
||||||
|
|
||||||
|
while (indices.isNotEmpty()) {
|
||||||
|
val v = indices.random(random)
|
||||||
|
shuffled.add(v)
|
||||||
|
indices.removeInt(indices.indexOf(v))
|
||||||
|
}
|
||||||
|
|
||||||
|
while (secondaryRegionCount-- > 0 && shuffled.isNotEmpty()) {
|
||||||
|
val regionName = secondaryRegionList[shuffled.removeInt(0)].asString
|
||||||
|
secondary.add(makeRegion(regionName, baseHeight))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val secondaryRegionSizeRange = vectors2d.fromJsonTree(layerConfig["secondaryRegionSize"])
|
||||||
|
val subRegionSizeRange = vectors2d.fromJsonTree(layerConfig["subRegionSize"])
|
||||||
|
|
||||||
|
val dungeonPool = dungeonPools.fromJsonTree(layerConfig.get("dungeons"))
|
||||||
|
val dungeonCountRange = if ("dungeonCountRange" in layerConfig) vectors2i.fromJsonTree(layerConfig["dungeonCountRange"]) else Vector2i.ZERO
|
||||||
|
val dungeonCount = random.nextRange(dungeonCountRange)
|
||||||
|
val dungeons = dungeonPool.sample(dungeonCount, random)
|
||||||
|
val dungeonXVariance = layerConfig.get("dungeonXVariance", 0)
|
||||||
|
|
||||||
|
return Layer(
|
||||||
|
layerLevel,
|
||||||
|
baseHeight,
|
||||||
|
ImmutableSet.copyOf(dungeons),
|
||||||
|
dungeonXVariance,
|
||||||
|
primary,
|
||||||
|
subPrimary,
|
||||||
|
secondary.stream().map { it.first }.collect(ImmutableList.toImmutableList()),
|
||||||
|
secondary.stream().map { it.second }.collect(ImmutableList.toImmutableList()),
|
||||||
|
secondaryRegionSizeRange,
|
||||||
|
subRegionSizeRange
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
val surfaceLayer = readLayer("surface") ?: throw NoSuchElementException("No such layer 'surface', this should never happen")
|
||||||
|
val primaryBiome = Registries.biomes.getOrThrow(surfaceLayer.primaryRegion.biome)
|
||||||
|
|
||||||
|
val parameters = TerrestrialWorldParameters()
|
||||||
|
|
||||||
|
parameters.threatLevel = threadLevel
|
||||||
|
parameters.typeName = typeName
|
||||||
|
parameters.worldSize = params.size
|
||||||
|
parameters.gravity = Vector2d(y = -random.nextRange(params.gravityRange))
|
||||||
|
parameters.airless = primaryBiome.value.airless
|
||||||
|
parameters.environmentStatusEffects = primaryBiome.value.statusEffects
|
||||||
|
parameters.overrideTech = params.overrideTech
|
||||||
|
parameters.globalDirectives = params.globalDirectives
|
||||||
|
parameters.beamUpRule = params.beamUpRule
|
||||||
|
parameters.disableDeathDrops = params.disableDeathDrops
|
||||||
|
parameters.worldEdgeForceRegions = params.worldEdgeForceRegions
|
||||||
|
parameters.weatherPool = primaryBiome.value.weather.stream().binnedChoice(threadLevel).get().random(random).value ?: throw NullPointerException("No weather pool")
|
||||||
|
parameters.primaryBiome = primaryBiome.key
|
||||||
|
parameters.sizeName = sizeName
|
||||||
|
parameters.hueShift = primaryBiome.value.hueShift(random)
|
||||||
|
|
||||||
|
parameters.primarySurfaceLiquid = surfaceLayer.primaryRegion.oceanLiquid ?: surfaceLayer.primaryRegion.caveLiquid
|
||||||
|
parameters.skyColoring = primaryBiome.value.skyColoring(random)
|
||||||
|
parameters.dayLength = random.nextRange(params.dayLengthRange)
|
||||||
|
|
||||||
|
parameters.blockNoiseConfig = params.blockNoise
|
||||||
|
parameters.blendNoiseConfig = params.blendNoise
|
||||||
|
parameters.blendSize = params.blendSize
|
||||||
|
|
||||||
|
parameters.spaceLayer = readLayer("space") ?: throw NoSuchElementException("No such terrain layer 'space'")
|
||||||
|
parameters.atmosphereLayer = readLayer("atmosphere") ?: throw NoSuchElementException("No such terrain layer 'atmosphere'")
|
||||||
|
parameters.surfaceLayer = surfaceLayer
|
||||||
|
parameters.subsurfaceLayer = readLayer("subsurface") ?: throw NoSuchElementException("No such terrain layer 'subsurface'")
|
||||||
|
parameters.coreLayer = readLayer("core") ?: throw NoSuchElementException("No such terrain layer 'core'")
|
||||||
|
|
||||||
|
val undergroundLayers = ArrayList<Layer>()
|
||||||
|
var ulayer = readLayer("underground${undergroundLayers.size + 1}")
|
||||||
|
|
||||||
|
while (ulayer != null) {
|
||||||
|
undergroundLayers.add(ulayer)
|
||||||
|
ulayer = readLayer("underground${undergroundLayers.size + 1}")
|
||||||
|
}
|
||||||
|
|
||||||
|
parameters.undergroundLayers = undergroundLayers
|
||||||
|
|
||||||
|
return parameters
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,16 @@
|
|||||||
|
package ru.dbotthepony.kstarbound.defs.world
|
||||||
|
|
||||||
|
import com.google.common.collect.ImmutableMap
|
||||||
|
import com.google.gson.JsonObject
|
||||||
|
import ru.dbotthepony.kstarbound.json.builder.JsonFactory
|
||||||
|
|
||||||
|
@JsonFactory
|
||||||
|
data class TerrestrialWorldsConfig(
|
||||||
|
val useSecondaryEnvironmentBiomeIndex: Boolean = false,
|
||||||
|
val regionDefaults: JsonObject,
|
||||||
|
val regionTypes: ImmutableMap<String, JsonObject>,
|
||||||
|
|
||||||
|
val planetDefaults: JsonObject,
|
||||||
|
val planetSizes: ImmutableMap<String, JsonObject>,
|
||||||
|
val planetTypes: ImmutableMap<String, JsonObject>,
|
||||||
|
)
|
@ -0,0 +1,121 @@
|
|||||||
|
package ru.dbotthepony.kstarbound.defs.world
|
||||||
|
|
||||||
|
import com.google.gson.JsonElement
|
||||||
|
import com.google.gson.JsonObject
|
||||||
|
import ru.dbotthepony.kstarbound.GlobalDefaults
|
||||||
|
import ru.dbotthepony.kstarbound.Registries
|
||||||
|
import ru.dbotthepony.kstarbound.Registry
|
||||||
|
import ru.dbotthepony.kstarbound.defs.AssetReference
|
||||||
|
import ru.dbotthepony.kstarbound.defs.ThingDescription
|
||||||
|
import ru.dbotthepony.kstarbound.defs.tile.TileDamageConfig
|
||||||
|
import ru.dbotthepony.kstarbound.json.builder.JsonFactory
|
||||||
|
import ru.dbotthepony.kstarbound.json.builder.JsonFlat
|
||||||
|
|
||||||
|
@JsonFactory
|
||||||
|
data class TreeVariant(
|
||||||
|
val stemName: String,
|
||||||
|
val foliageName: String,
|
||||||
|
|
||||||
|
val stemDirectory: String,
|
||||||
|
// may i fucking ask you why do you embed ENTIRE FUCKING FILE in
|
||||||
|
// this struct, Chucklefuck???????
|
||||||
|
val stemSettings: JsonElement,
|
||||||
|
val stemHueShift: Double,
|
||||||
|
|
||||||
|
val foliageDirectory: String,
|
||||||
|
// AGAIN.
|
||||||
|
val foliageSettings: FoliageData,
|
||||||
|
val foliageHueShift: Double,
|
||||||
|
|
||||||
|
@JsonFlat
|
||||||
|
val descriptions: ThingDescription,
|
||||||
|
val ceiling: Boolean,
|
||||||
|
|
||||||
|
val ephemeral: Boolean,
|
||||||
|
|
||||||
|
val stemDropConfig: JsonElement,
|
||||||
|
val foliageDropConfig: JsonElement,
|
||||||
|
|
||||||
|
val tileDamageParameters: TileDamageConfig,
|
||||||
|
) {
|
||||||
|
@JsonFactory
|
||||||
|
data class StemData(
|
||||||
|
val name: String,
|
||||||
|
val ceiling: Boolean = false,
|
||||||
|
val dropConfig: JsonElement = JsonObject(),
|
||||||
|
val shape: String,
|
||||||
|
|
||||||
|
// TODO: original engine uses 'ephemeral' when creating tree variant with stem only,
|
||||||
|
// TODO: and uses 'allowsBlockPlacement' when creating tree with stem and foliage
|
||||||
|
// TODO: Bro what the FUCK?
|
||||||
|
val ephemeral: Boolean = false,
|
||||||
|
// val allowsBlockPlacement: Boolean = false,
|
||||||
|
|
||||||
|
@JsonFlat
|
||||||
|
val descriptions: ThingDescription,
|
||||||
|
|
||||||
|
val damageTable: AssetReference<TileDamageConfig>? = null,
|
||||||
|
val health: Double = 1.0,
|
||||||
|
)
|
||||||
|
|
||||||
|
@JsonFactory
|
||||||
|
data class FoliageData(
|
||||||
|
val name: String,
|
||||||
|
val dropConfig: JsonElement = JsonObject(),
|
||||||
|
val parallaxFoliage: Boolean = false,
|
||||||
|
val shape: String,
|
||||||
|
)
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
fun create(stemName: String, stemHueShift: Double): TreeVariant {
|
||||||
|
return create(Registries.treeStemVariants.getOrThrow(stemName), stemHueShift)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun create(data: Registry.Entry<StemData>, stemHueShift: Double): TreeVariant {
|
||||||
|
return TreeVariant(
|
||||||
|
stemDirectory = data.file?.computeDirectory() ?: "/",
|
||||||
|
stemSettings = data.json.deepCopy(),
|
||||||
|
stemHueShift = stemHueShift,
|
||||||
|
ceiling = data.value.ceiling,
|
||||||
|
stemDropConfig = data.value.dropConfig.deepCopy(),
|
||||||
|
descriptions = data.value.descriptions.fixDescription(data.key),
|
||||||
|
ephemeral = data.value.ephemeral,
|
||||||
|
tileDamageParameters = (data.value.damageTable?.value ?: GlobalDefaults.treeDamage).copy(totalHealth = data.value.health),
|
||||||
|
|
||||||
|
foliageSettings = FoliageData("", shape = ""),
|
||||||
|
foliageDropConfig = JsonObject(),
|
||||||
|
foliageName = "",
|
||||||
|
foliageDirectory = "/",
|
||||||
|
foliageHueShift = 0.0,
|
||||||
|
stemName = data.key,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun create(stemName: String, stemHueShift: Double, foliageName: String, foliageHueShift: Double): TreeVariant {
|
||||||
|
val data = Registries.treeStemVariants.getOrThrow(stemName)
|
||||||
|
val fdata = Registries.treeFoliageVariants.getOrThrow(foliageName)
|
||||||
|
|
||||||
|
return create(data, stemHueShift, fdata, foliageHueShift)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun create(data: Registry.Entry<StemData>, stemHueShift: Double, fdata: Registry.Entry<FoliageData>, foliageHueShift: Double): TreeVariant {
|
||||||
|
return TreeVariant(
|
||||||
|
stemDirectory = data.file?.computeDirectory() ?: "/",
|
||||||
|
stemSettings = data.json.deepCopy(),
|
||||||
|
stemHueShift = stemHueShift,
|
||||||
|
ceiling = data.value.ceiling,
|
||||||
|
stemDropConfig = data.value.dropConfig.deepCopy(),
|
||||||
|
descriptions = data.value.descriptions.fixDescription("${data.key} with ${fdata.key}"),
|
||||||
|
ephemeral = data.value.ephemeral,
|
||||||
|
tileDamageParameters = (data.value.damageTable?.value ?: GlobalDefaults.treeDamage).copy(totalHealth = data.value.health),
|
||||||
|
|
||||||
|
foliageSettings = fdata.value,
|
||||||
|
foliageDropConfig = fdata.value.dropConfig.deepCopy(),
|
||||||
|
foliageName = fdata.key,
|
||||||
|
foliageDirectory = fdata.file?.computeDirectory() ?: "/",
|
||||||
|
foliageHueShift = foliageHueShift,
|
||||||
|
stemName = data.key,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,33 @@
|
|||||||
|
package ru.dbotthepony.kstarbound.defs.world
|
||||||
|
|
||||||
|
import ru.dbotthepony.kstarbound.defs.PerlinNoiseParameters
|
||||||
|
import ru.dbotthepony.kstarbound.json.builder.JsonFactory
|
||||||
|
import ru.dbotthepony.kstarbound.util.random.AbstractPerlinNoise
|
||||||
|
import ru.dbotthepony.kstarbound.util.random.random
|
||||||
|
import java.util.random.RandomGenerator
|
||||||
|
|
||||||
|
@JsonFactory
|
||||||
|
data class BlockNoiseConfig(
|
||||||
|
val horizontalNoise: PerlinNoiseParameters,
|
||||||
|
val verticalNoise: PerlinNoiseParameters,
|
||||||
|
val noise: PerlinNoiseParameters,
|
||||||
|
) {
|
||||||
|
fun build(random: RandomGenerator): BlockNoise {
|
||||||
|
return BlockNoise(
|
||||||
|
AbstractPerlinNoise.of(horizontalNoise).also { it.init(random.nextLong()) },
|
||||||
|
AbstractPerlinNoise.of(verticalNoise).also { it.init(random.nextLong()) },
|
||||||
|
AbstractPerlinNoise.of(noise).also { it.init(random.nextLong()) },
|
||||||
|
AbstractPerlinNoise.of(noise).also { it.init(random.nextLong()) },
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun build(seed: Long) = build(random(seed))
|
||||||
|
}
|
||||||
|
|
||||||
|
@JsonFactory
|
||||||
|
data class BlockNoise(
|
||||||
|
val horizontalNoise: AbstractPerlinNoise,
|
||||||
|
val verticalNoise: AbstractPerlinNoise,
|
||||||
|
val xNoise: AbstractPerlinNoise,
|
||||||
|
val yNoise: AbstractPerlinNoise,
|
||||||
|
)
|
@ -0,0 +1,196 @@
|
|||||||
|
package ru.dbotthepony.kstarbound.defs.world
|
||||||
|
|
||||||
|
import com.google.gson.Gson
|
||||||
|
import com.google.gson.JsonObject
|
||||||
|
import com.google.gson.TypeAdapter
|
||||||
|
import com.google.gson.TypeAdapterFactory
|
||||||
|
import com.google.gson.reflect.TypeToken
|
||||||
|
import com.google.gson.stream.JsonReader
|
||||||
|
import com.google.gson.stream.JsonWriter
|
||||||
|
import ru.dbotthepony.kommons.gson.consumeNull
|
||||||
|
import ru.dbotthepony.kommons.gson.set
|
||||||
|
import ru.dbotthepony.kommons.gson.value
|
||||||
|
import ru.dbotthepony.kommons.util.Either
|
||||||
|
import ru.dbotthepony.kommons.vector.Vector2d
|
||||||
|
import ru.dbotthepony.kommons.vector.Vector2i
|
||||||
|
import ru.dbotthepony.kstarbound.Starbound
|
||||||
|
import ru.dbotthepony.kstarbound.collect.WeightedList
|
||||||
|
import ru.dbotthepony.kstarbound.json.builder.DispatchingAdapter
|
||||||
|
import ru.dbotthepony.kstarbound.json.builder.IStringSerializable
|
||||||
|
import ru.dbotthepony.kstarbound.json.builder.JsonFactory
|
||||||
|
import kotlin.properties.Delegates
|
||||||
|
|
||||||
|
enum class BeamUpRule(val jsonName: String) : IStringSerializable {
|
||||||
|
NOWHERE("nowhere"),
|
||||||
|
SURFACE("surface"),
|
||||||
|
ANYWHERE("anywhere"),
|
||||||
|
ANYWHERE_WITH_WARNING("anywherewithwarning");
|
||||||
|
|
||||||
|
override fun match(name: String): Boolean {
|
||||||
|
return name.lowercase() == jsonName
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun write(out: JsonWriter) {
|
||||||
|
out.value(jsonName)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
enum class WorldEdgeForceRegion(val sname: String) : IStringSerializable {
|
||||||
|
NONE("none"),
|
||||||
|
TOP("top"),
|
||||||
|
BOTTOM("bottom"),
|
||||||
|
TOP_AND_BOTTOM("topandbottom");
|
||||||
|
|
||||||
|
override fun match(name: String): Boolean {
|
||||||
|
return name.lowercase() == sname
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun write(out: JsonWriter) {
|
||||||
|
out.value(sname)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
enum class VisitableWorldParametersType(val jsonName: String, val token: TypeToken<out VisitableWorldParameters>) : IStringSerializable {
|
||||||
|
TERRESTRIAL("TerrestrialWorldParameters", TypeToken.get(TerrestrialWorldParameters::class.java)),
|
||||||
|
ASTEROIDS("AsteroidsWorldParameters", TypeToken.get(AsteroidsWorldParameters::class.java)),
|
||||||
|
FLOATING_DUNGEON("FloatingDungeonWorldParameters", TypeToken.get(FloatingDungeonWorldParameters::class.java));
|
||||||
|
|
||||||
|
override fun match(name: String): Boolean {
|
||||||
|
return name == jsonName
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun write(out: JsonWriter) {
|
||||||
|
out.value(jsonName)
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object : TypeAdapterFactory {
|
||||||
|
val ADAPTER = DispatchingAdapter("type", { type }, { token }, entries)
|
||||||
|
|
||||||
|
override fun <T : Any> create(gson: Gson, type: TypeToken<T>): TypeAdapter<T>? {
|
||||||
|
if (VisitableWorldParameters::class.java.isAssignableFrom(type.rawType)) {
|
||||||
|
return object : TypeAdapter<VisitableWorldParameters>() {
|
||||||
|
private val objects = gson.getAdapter(JsonObject::class.java)
|
||||||
|
|
||||||
|
override fun write(out: JsonWriter, value: VisitableWorldParameters?) {
|
||||||
|
if (value == null)
|
||||||
|
out.nullValue()
|
||||||
|
else
|
||||||
|
out.value(value.toJson(false))
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun read(`in`: JsonReader): VisitableWorldParameters? {
|
||||||
|
if (`in`.consumeNull())
|
||||||
|
return null
|
||||||
|
|
||||||
|
val instance = type.rawType.getDeclaredConstructor().newInstance() as VisitableWorldParameters
|
||||||
|
instance.fromJson(objects.read(`in`))
|
||||||
|
return instance
|
||||||
|
}
|
||||||
|
} as TypeAdapter<T>
|
||||||
|
}
|
||||||
|
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// using notnull delegate because this will look very ugly
|
||||||
|
// if done as immutable class
|
||||||
|
abstract class VisitableWorldParameters {
|
||||||
|
var threatLevel: Double = 0.0
|
||||||
|
protected set
|
||||||
|
var typeName: String by Delegates.notNull()
|
||||||
|
protected set
|
||||||
|
var worldSize: Vector2i by Delegates.notNull()
|
||||||
|
protected set
|
||||||
|
var gravity: Vector2d = Vector2d.ZERO
|
||||||
|
protected set
|
||||||
|
var airless: Boolean = false
|
||||||
|
protected set
|
||||||
|
var environmentStatusEffects: Set<String> by Delegates.notNull()
|
||||||
|
protected set
|
||||||
|
var overrideTech: Set<String>? = null
|
||||||
|
protected set
|
||||||
|
var globalDirectives: Set<String>? = null
|
||||||
|
protected set
|
||||||
|
var beamUpRule: BeamUpRule by Delegates.notNull()
|
||||||
|
protected set
|
||||||
|
var disableDeathDrops: Boolean = false
|
||||||
|
protected set
|
||||||
|
var terraformed: Boolean = false
|
||||||
|
protected set
|
||||||
|
var worldEdgeForceRegions: WorldEdgeForceRegion = WorldEdgeForceRegion.NONE
|
||||||
|
protected set
|
||||||
|
var weatherPool: WeightedList<String>? = null
|
||||||
|
protected set
|
||||||
|
|
||||||
|
abstract fun createLayout(seed: Long): WorldLayout
|
||||||
|
|
||||||
|
abstract val type: VisitableWorldParametersType
|
||||||
|
|
||||||
|
// lazy but effective, and less error prone
|
||||||
|
@JsonFactory
|
||||||
|
data class StoreData(
|
||||||
|
val threatLevel: Double,
|
||||||
|
val typeName: String,
|
||||||
|
val worldSize: Vector2i,
|
||||||
|
val gravity: Either<Double, Vector2d>,
|
||||||
|
val airless: Boolean,
|
||||||
|
val environmentStatusEffects: Set<String>,
|
||||||
|
val overrideTech: Set<String>?,
|
||||||
|
val globalDirectives: Set<String>?,
|
||||||
|
val beamUpRule: BeamUpRule,
|
||||||
|
val disableDeathDrops: Boolean,
|
||||||
|
val terraformed: Boolean,
|
||||||
|
val worldEdgeForceRegions: WorldEdgeForceRegion,
|
||||||
|
val weatherPool: WeightedList<String>?,
|
||||||
|
)
|
||||||
|
|
||||||
|
open fun fromJson(data: JsonObject) {
|
||||||
|
val read = Starbound.gson.fromJson(data, StoreData::class.java)
|
||||||
|
|
||||||
|
this.threatLevel = read.threatLevel
|
||||||
|
this.typeName = read.typeName
|
||||||
|
this.worldSize = read.worldSize
|
||||||
|
this.gravity = read.gravity.map({ Vector2d(y = it) }, { it })
|
||||||
|
this.airless = read.airless
|
||||||
|
this.environmentStatusEffects = read.environmentStatusEffects
|
||||||
|
this.overrideTech = read.overrideTech
|
||||||
|
this.globalDirectives = read.globalDirectives
|
||||||
|
this.beamUpRule = read.beamUpRule
|
||||||
|
this.disableDeathDrops = read.disableDeathDrops
|
||||||
|
this.terraformed = read.terraformed
|
||||||
|
this.worldEdgeForceRegions = read.worldEdgeForceRegions
|
||||||
|
this.weatherPool = read.weatherPool
|
||||||
|
}
|
||||||
|
|
||||||
|
open fun toJson(data: JsonObject, isLegacy: Boolean) {
|
||||||
|
val store = StoreData(
|
||||||
|
threatLevel,
|
||||||
|
typeName,
|
||||||
|
worldSize,
|
||||||
|
if (isLegacy) Either.left(gravity.y) else Either.right(gravity),
|
||||||
|
airless,
|
||||||
|
environmentStatusEffects,
|
||||||
|
overrideTech,
|
||||||
|
globalDirectives,
|
||||||
|
beamUpRule,
|
||||||
|
disableDeathDrops,
|
||||||
|
terraformed,
|
||||||
|
worldEdgeForceRegions,
|
||||||
|
weatherPool,
|
||||||
|
)
|
||||||
|
|
||||||
|
for ((k, v) in (Starbound.gson.toJsonTree(store) as JsonObject).entrySet()) {
|
||||||
|
data[k] = v
|
||||||
|
}
|
||||||
|
|
||||||
|
data["type"] = type.jsonName
|
||||||
|
}
|
||||||
|
|
||||||
|
fun toJson(isLegacy: Boolean): JsonObject {
|
||||||
|
val data = JsonObject()
|
||||||
|
toJson(data, isLegacy)
|
||||||
|
return data
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,417 @@
|
|||||||
|
package ru.dbotthepony.kstarbound.defs.world
|
||||||
|
|
||||||
|
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.reflect.TypeToken
|
||||||
|
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.contains
|
||||||
|
import ru.dbotthepony.kommons.gson.get
|
||||||
|
import ru.dbotthepony.kommons.gson.getArray
|
||||||
|
import ru.dbotthepony.kommons.gson.set
|
||||||
|
import ru.dbotthepony.kommons.math.RGBAColor
|
||||||
|
import ru.dbotthepony.kommons.util.AABBi
|
||||||
|
import ru.dbotthepony.kommons.util.Either
|
||||||
|
import ru.dbotthepony.kommons.vector.Vector2d
|
||||||
|
import ru.dbotthepony.kommons.vector.Vector2i
|
||||||
|
import ru.dbotthepony.kstarbound.GlobalDefaults
|
||||||
|
import ru.dbotthepony.kstarbound.Registries
|
||||||
|
import ru.dbotthepony.kstarbound.Starbound
|
||||||
|
import ru.dbotthepony.kstarbound.defs.world.terrain.AbstractTerrainSelector
|
||||||
|
import ru.dbotthepony.kstarbound.defs.world.terrain.Biome
|
||||||
|
import ru.dbotthepony.kstarbound.defs.world.terrain.TerrainSelectorParameters
|
||||||
|
import ru.dbotthepony.kstarbound.defs.world.terrain.createNamedTerrainSelector
|
||||||
|
import ru.dbotthepony.kstarbound.fromJson
|
||||||
|
import ru.dbotthepony.kstarbound.json.builder.JsonFactory
|
||||||
|
import ru.dbotthepony.kstarbound.util.ListInterner
|
||||||
|
import ru.dbotthepony.kstarbound.util.random.AbstractPerlinNoise
|
||||||
|
import ru.dbotthepony.kstarbound.util.random.nextRange
|
||||||
|
import java.util.random.RandomGenerator
|
||||||
|
import kotlin.math.roundToInt
|
||||||
|
import kotlin.properties.Delegates
|
||||||
|
|
||||||
|
/**
|
||||||
|
* While this class seems redundant, it is not.
|
||||||
|
*
|
||||||
|
* Every world must be represented by layers and regions.
|
||||||
|
* And some worlds don't have visitable world parameters because they are custom-made,
|
||||||
|
* such as dungeon worlds.
|
||||||
|
*
|
||||||
|
* Layer is a "line" than spans from 0 to world width, and has determined height.
|
||||||
|
* Region, on other hand, is 1D section (slice) of layer, representing unique biome.
|
||||||
|
* This structure allows to have diverse worlds, but regions are limited to cover only one
|
||||||
|
* layer at time.
|
||||||
|
*
|
||||||
|
* While final region structure can be anything, generated terrestrial worlds
|
||||||
|
* regions are generated in following pattern (per layer):
|
||||||
|
* `[... [main region 0][sub region 0][main region 0] [main region 1][sub region 1][main region 1] ...]`
|
||||||
|
*
|
||||||
|
* Sucks that it has to virtually duplicate everything when creating from
|
||||||
|
* [TerrestrialWorldParameters], though.
|
||||||
|
*
|
||||||
|
* World regions terrain selectors and selectors themselves are stored separately,
|
||||||
|
* and hence create weird "index" structure in original code. This was done to
|
||||||
|
* greatly cut down serialized form size. We, however, don't do this at runtime,
|
||||||
|
* and reference biomes/terrain selectors directly.
|
||||||
|
*/
|
||||||
|
class WorldLayout {
|
||||||
|
var worldSize: Vector2i by Delegates.notNull()
|
||||||
|
var regionBlending: Double = 0.0
|
||||||
|
var blendNoise: AbstractPerlinNoise? = null
|
||||||
|
var blockNoise: BlockNoise? = null
|
||||||
|
val terrainSelectors = ListInterner<AbstractTerrainSelector<*>>()
|
||||||
|
val biomes = ListInterner<Biome>()
|
||||||
|
|
||||||
|
val playerStartSearchRegions = ArrayList<AABBi>()
|
||||||
|
val layers = ArrayList<Layer>()
|
||||||
|
|
||||||
|
private object StartingRegionsToken : TypeToken<ArrayList<AABBi>>()
|
||||||
|
|
||||||
|
@JsonFactory
|
||||||
|
data class SerializedLayer(
|
||||||
|
val yStart: Int,
|
||||||
|
val boundaries: IntArrayList,
|
||||||
|
val cells: ImmutableList<JsonObject>,
|
||||||
|
)
|
||||||
|
|
||||||
|
inner class Layer(val yStart: Int) : Comparable<Layer> {
|
||||||
|
val boundaries = IntArrayList()
|
||||||
|
val cells = ArrayList<Region>()
|
||||||
|
|
||||||
|
override fun compareTo(other: Layer): Int {
|
||||||
|
return yStart.compareTo(other.yStart)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun toJson(isLegacy: Boolean): JsonObject {
|
||||||
|
val data = JsonObject()
|
||||||
|
|
||||||
|
data["yStart"] = yStart
|
||||||
|
data["boundaries"] = boundaries.stream().map { JsonPrimitive(it) }.collect(JsonArrayCollector)
|
||||||
|
data["cells"] = cells.stream().map { it.toJson(isLegacy) }.collect(JsonArrayCollector)
|
||||||
|
|
||||||
|
return data
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@JsonFactory
|
||||||
|
data class RegionLiquids(
|
||||||
|
val caveLiquid: Either<Int, String>? = null,
|
||||||
|
val caveLiquidSeedDensity: Double = 0.0,
|
||||||
|
|
||||||
|
val oceanLiquid: Either<Int, String>? = null,
|
||||||
|
val oceanLiquidLevel: Int = 0,
|
||||||
|
|
||||||
|
val encloseLiquids: Boolean = false,
|
||||||
|
val fillMicrodungeons: Boolean = false,
|
||||||
|
) {
|
||||||
|
fun toLegacy(): RegionLiquidsLegacy {
|
||||||
|
return RegionLiquidsLegacy(
|
||||||
|
caveLiquid = caveLiquid?.map({ it }, { Registries.liquid[it]?.id }) ?: 0,
|
||||||
|
caveLiquidSeedDensity = caveLiquidSeedDensity,
|
||||||
|
oceanLiquid = oceanLiquid?.map({ it }, { Registries.liquid[it]?.id }) ?: 0,
|
||||||
|
oceanLiquidLevel = oceanLiquidLevel,
|
||||||
|
encloseLiquids = encloseLiquids,
|
||||||
|
fillMicrodungeons = fillMicrodungeons,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@JsonFactory
|
||||||
|
data class RegionLiquidsLegacy(
|
||||||
|
val caveLiquid: Int = 0,
|
||||||
|
val caveLiquidSeedDensity: Double = 0.0,
|
||||||
|
|
||||||
|
val oceanLiquid: Int = 0,
|
||||||
|
val oceanLiquidLevel: Int = 0,
|
||||||
|
|
||||||
|
val encloseLiquids: Boolean = false,
|
||||||
|
val fillMicrodungeons: Boolean = false,
|
||||||
|
)
|
||||||
|
|
||||||
|
@JsonFactory
|
||||||
|
data class SerializedRegion(
|
||||||
|
val terrainSelectorIndex: Int,
|
||||||
|
val foregroundCaveSelectorIndex: Int,
|
||||||
|
val backgroundCaveSelectorIndex: Int,
|
||||||
|
|
||||||
|
val blockBiomeIndex: Int,
|
||||||
|
val environmentBiomeIndex: Int,
|
||||||
|
|
||||||
|
val subBlockSelectorIndexes: IntArrayList,
|
||||||
|
val foregroundOreSelectorIndexes: IntArrayList,
|
||||||
|
val backgroundOreSelectorIndexes: IntArrayList,
|
||||||
|
)
|
||||||
|
|
||||||
|
inner class Region(
|
||||||
|
val terrainSelector: AbstractTerrainSelector<*>?,
|
||||||
|
val foregroundCaveSelector: AbstractTerrainSelector<*>?,
|
||||||
|
val backgroundCaveSelector: AbstractTerrainSelector<*>?,
|
||||||
|
|
||||||
|
val blockBiome: Biome?,
|
||||||
|
var environmentBiome: Biome?,
|
||||||
|
|
||||||
|
val subBlockSelector: List<AbstractTerrainSelector<*>>,
|
||||||
|
val foregroundOreSelector: List<AbstractTerrainSelector<*>>,
|
||||||
|
val backgroundOreSelector: List<AbstractTerrainSelector<*>>,
|
||||||
|
|
||||||
|
val regionLiquids: RegionLiquids,
|
||||||
|
) {
|
||||||
|
fun toJson(isLegacy: Boolean): JsonObject {
|
||||||
|
val data = SerializedRegion(
|
||||||
|
terrainSelectorIndex = terrainSelectors.list.indexOf(terrainSelector),
|
||||||
|
foregroundCaveSelectorIndex = terrainSelectors.list.indexOf(foregroundCaveSelector),
|
||||||
|
backgroundCaveSelectorIndex = terrainSelectors.list.indexOf(backgroundCaveSelector),
|
||||||
|
blockBiomeIndex = biomes.list.indexOf(blockBiome),
|
||||||
|
environmentBiomeIndex = biomes.list.indexOf(environmentBiome),
|
||||||
|
subBlockSelectorIndexes = subBlockSelector.stream().mapToInt { terrainSelectors.list.indexOf(it) }.filter { it != -1 }.collect(::IntArrayList, IntArrayList::add, IntArrayList::addAll),
|
||||||
|
foregroundOreSelectorIndexes = foregroundOreSelector.stream().mapToInt { terrainSelectors.list.indexOf(it) }.filter { it != -1 }.collect(::IntArrayList, IntArrayList::add, IntArrayList::addAll),
|
||||||
|
backgroundOreSelectorIndexes = backgroundOreSelector.stream().mapToInt { terrainSelectors.list.indexOf(it) }.filter { it != -1 }.collect(::IntArrayList, IntArrayList::add, IntArrayList::addAll),
|
||||||
|
)
|
||||||
|
|
||||||
|
val liquidData = (if (isLegacy) Starbound.gson.toJsonTree(regionLiquids.toLegacy()) else Starbound.gson.toJsonTree(regionLiquids)) as JsonObject
|
||||||
|
val data2 = Starbound.gson.toJsonTree(data) as JsonObject
|
||||||
|
|
||||||
|
for ((k, v) in data2.entrySet()) {
|
||||||
|
liquidData[k] = v
|
||||||
|
}
|
||||||
|
|
||||||
|
return liquidData
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
data class RegionParameters(
|
||||||
|
val baseHeight: Int,
|
||||||
|
val threatLevel: Double,
|
||||||
|
val biomeName: String?,
|
||||||
|
val terrainSelector: String?,
|
||||||
|
val fgCaveSelector: String?,
|
||||||
|
val bgCaveSelector: String?,
|
||||||
|
val fgOreSelector: String?,
|
||||||
|
val bgOreSelector: String?,
|
||||||
|
val subBlockSelector: String?,
|
||||||
|
val regionLiquids: RegionLiquids,
|
||||||
|
)
|
||||||
|
|
||||||
|
@JsonFactory
|
||||||
|
data class SerializedForm(
|
||||||
|
val worldSize: Vector2i,
|
||||||
|
val regionBlending: Double,
|
||||||
|
val blockNoise: BlockNoise? = null,
|
||||||
|
val blendNoise: AbstractPerlinNoise? = null,
|
||||||
|
val playerStartSearchRegions: List<AABBi>,
|
||||||
|
val biomes: List<Biome>,
|
||||||
|
val terrainSelectors: List<AbstractTerrainSelector<*>>,
|
||||||
|
val layers: JsonArray,
|
||||||
|
)
|
||||||
|
|
||||||
|
fun toJson(isLegacy: Boolean): JsonObject {
|
||||||
|
return Starbound.gson.toJsonTree(SerializedForm(
|
||||||
|
worldSize, regionBlending, blockNoise, blendNoise,
|
||||||
|
playerStartSearchRegions, biomes.list, terrainSelectors.list,
|
||||||
|
layers = layers.stream().map { it.toJson(isLegacy) }.collect(JsonArrayCollector)
|
||||||
|
)) as JsonObject
|
||||||
|
}
|
||||||
|
|
||||||
|
fun fromJson(data: JsonObject) {
|
||||||
|
val load = Starbound.gson.fromJson(data, SerializedForm::class.java)
|
||||||
|
|
||||||
|
worldSize = load.worldSize
|
||||||
|
regionBlending = load.regionBlending
|
||||||
|
blockNoise = load.blockNoise
|
||||||
|
blendNoise = load.blendNoise
|
||||||
|
playerStartSearchRegions.addAll(load.playerStartSearchRegions)
|
||||||
|
|
||||||
|
load.layers.forEach {
|
||||||
|
val datalayer = Starbound.gson.fromJson(it, SerializedLayer::class.java)
|
||||||
|
val layer = Layer(datalayer.yStart)
|
||||||
|
layer.boundaries.addAll(datalayer.boundaries)
|
||||||
|
|
||||||
|
datalayer.cells.forEach {
|
||||||
|
val region = Starbound.gson.fromJson(it, SerializedRegion::class.java)
|
||||||
|
|
||||||
|
layer.cells.add(Region(
|
||||||
|
terrainSelector = load.terrainSelectors.getOrNull(region.terrainSelectorIndex)?.let { terrainSelectors.intern(it) },
|
||||||
|
foregroundCaveSelector = load.terrainSelectors.getOrNull(region.foregroundCaveSelectorIndex)?.let { terrainSelectors.intern(it) },
|
||||||
|
backgroundCaveSelector = load.terrainSelectors.getOrNull(region.backgroundCaveSelectorIndex)?.let { terrainSelectors.intern(it) },
|
||||||
|
blockBiome = load.biomes.getOrNull(region.blockBiomeIndex)?.let { biomes.intern(it) },
|
||||||
|
environmentBiome = load.biomes.getOrNull(region.environmentBiomeIndex)?.let { biomes.intern(it) },
|
||||||
|
subBlockSelector = region.subBlockSelectorIndexes.map { load.terrainSelectors.getOrNull(it)?.let { terrainSelectors.intern(it) } }.filterNotNull(),
|
||||||
|
foregroundOreSelector = region.foregroundOreSelectorIndexes.map { load.terrainSelectors.getOrNull(it)?.let { terrainSelectors.intern(it) } }.filterNotNull(),
|
||||||
|
backgroundOreSelector = region.backgroundOreSelectorIndexes.map { load.terrainSelectors.getOrNull(it)?.let { terrainSelectors.intern(it) } }.filterNotNull(),
|
||||||
|
regionLiquids = Starbound.gson.fromJson(it, RegionLiquids::class.java),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun buildRegion(random: RandomGenerator, params: RegionParameters): Region {
|
||||||
|
var terrainSelector: AbstractTerrainSelector<*>? = null
|
||||||
|
var foregroundCaveSelector: AbstractTerrainSelector<*>? = null
|
||||||
|
var backgroundCaveSelector: AbstractTerrainSelector<*>? = null
|
||||||
|
|
||||||
|
val terrainBase = TerrainSelectorParameters(worldSize.x, params.baseHeight.toDouble())
|
||||||
|
val terrain = terrainBase.withSeed(random.nextLong())
|
||||||
|
val fg = terrainBase.withSeed(random.nextLong())
|
||||||
|
val bg = terrainBase.withSeed(random.nextLong())
|
||||||
|
|
||||||
|
if (params.terrainSelector != null)
|
||||||
|
terrainSelector = terrainSelectors.intern(createNamedTerrainSelector(params.terrainSelector, terrain))
|
||||||
|
if (params.fgCaveSelector != null)
|
||||||
|
foregroundCaveSelector = terrainSelectors.intern(createNamedTerrainSelector(params.fgCaveSelector, fg))
|
||||||
|
if (params.bgCaveSelector != null)
|
||||||
|
backgroundCaveSelector = terrainSelectors.intern(createNamedTerrainSelector(params.bgCaveSelector, bg))
|
||||||
|
|
||||||
|
val subBlockSelector = ArrayList<AbstractTerrainSelector<*>>()
|
||||||
|
val foregroundOreSelector = ArrayList<AbstractTerrainSelector<*>>()
|
||||||
|
val backgroundOreSelector = ArrayList<AbstractTerrainSelector<*>>()
|
||||||
|
|
||||||
|
var biome: Biome? = null
|
||||||
|
|
||||||
|
if (params.biomeName != null) {
|
||||||
|
biome = biomes.intern(Registries.biomes.getOrThrow(params.biomeName).value.create(random, params.baseHeight, params.threatLevel))
|
||||||
|
|
||||||
|
if (params.subBlockSelector != null) {
|
||||||
|
for (i in 0 until biome.subBlocks.size) {
|
||||||
|
subBlockSelector.add(terrainSelectors.intern(createNamedTerrainSelector(params.subBlockSelector, terrainBase.withSeed(random.nextLong()))))
|
||||||
|
}
|
||||||
|
|
||||||
|
for ((ore, commonality) in biome.ores) {
|
||||||
|
val oreParams = terrainBase.withCommonality(commonality)
|
||||||
|
|
||||||
|
if (params.fgOreSelector != null) {
|
||||||
|
foregroundOreSelector.add(terrainSelectors.intern(createNamedTerrainSelector(params.fgOreSelector, oreParams.withSeed(random.nextLong()))))
|
||||||
|
}
|
||||||
|
|
||||||
|
if (params.bgOreSelector != null) {
|
||||||
|
backgroundOreSelector.add(terrainSelectors.intern(createNamedTerrainSelector(params.bgOreSelector, oreParams.withSeed(random.nextLong()))))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return Region(
|
||||||
|
terrainSelector = terrainSelector,
|
||||||
|
foregroundCaveSelector = foregroundCaveSelector,
|
||||||
|
backgroundCaveSelector = backgroundCaveSelector,
|
||||||
|
|
||||||
|
subBlockSelector = subBlockSelector,
|
||||||
|
foregroundOreSelector = foregroundOreSelector,
|
||||||
|
backgroundOreSelector = backgroundOreSelector,
|
||||||
|
|
||||||
|
blockBiome = biome,
|
||||||
|
environmentBiome = biome,
|
||||||
|
|
||||||
|
regionLiquids = params.regionLiquids
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun addLayer(random: RandomGenerator, yStart: Int, params: RegionParameters) {
|
||||||
|
val layer = Layer(yStart)
|
||||||
|
layer.cells.add(buildRegion(random, params))
|
||||||
|
layers.add(layer)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun addLayer(
|
||||||
|
random: RandomGenerator, yStart: Int, yBase: Int, primaryBiome: String,
|
||||||
|
primaryRegionParams: RegionParameters, primarySubRegionParams: RegionParameters,
|
||||||
|
secondaryRegions: List<RegionParameters>, secondarySubRegions: List<RegionParameters>,
|
||||||
|
secondaryRegionSize: Vector2d, subRegionSize: Vector2d
|
||||||
|
) {
|
||||||
|
require(secondaryRegions.size == secondarySubRegions.size) { "${secondaryRegions.size} != ${secondarySubRegions.size}" }
|
||||||
|
require(subRegionSize.y <= 1.0) { "subRegionSize.y > 1.0: ${subRegionSize.y}" }
|
||||||
|
|
||||||
|
val layer = Layer(yStart)
|
||||||
|
val relativeRegionSizes = DoubleArrayList()
|
||||||
|
var totalRelativeSize = 0.0
|
||||||
|
|
||||||
|
val primaryEnvironment = buildRegion(random, primaryRegionParams)
|
||||||
|
val spawnBiomes = ObjectOpenHashSet<Biome>()
|
||||||
|
|
||||||
|
fun addRegion(params: RegionParameters, subParams: RegionParameters, regionSizeRange: Vector2d) {
|
||||||
|
val region = buildRegion(random, params)
|
||||||
|
val subRegion = buildRegion(random, params)
|
||||||
|
|
||||||
|
if (!GlobalDefaults.terrestrialWorlds.useSecondaryEnvironmentBiomeIndex) {
|
||||||
|
region.environmentBiome = primaryEnvironment.environmentBiome
|
||||||
|
}
|
||||||
|
|
||||||
|
subRegion.environmentBiome = region.environmentBiome
|
||||||
|
|
||||||
|
if (params.biomeName == primaryBiome && region.blockBiome != null)
|
||||||
|
spawnBiomes.add(region.blockBiome)
|
||||||
|
|
||||||
|
if (subParams.biomeName == primaryBiome && subRegion.blockBiome != null)
|
||||||
|
spawnBiomes.add(subRegion.blockBiome)
|
||||||
|
|
||||||
|
layer.cells.add(region)
|
||||||
|
layer.cells.add(subRegion)
|
||||||
|
layer.cells.add(region)
|
||||||
|
|
||||||
|
var regionRelativeSize = random.nextRange(regionSizeRange)
|
||||||
|
var subRegionRelativeSize = random.nextRange(subRegionSize)
|
||||||
|
|
||||||
|
totalRelativeSize += regionRelativeSize
|
||||||
|
subRegionRelativeSize *= regionRelativeSize
|
||||||
|
regionRelativeSize -= subRegionRelativeSize
|
||||||
|
|
||||||
|
relativeRegionSizes.add(regionRelativeSize / 2.0)
|
||||||
|
relativeRegionSizes.add(subRegionRelativeSize)
|
||||||
|
relativeRegionSizes.add(regionRelativeSize / 2.0)
|
||||||
|
}
|
||||||
|
|
||||||
|
// construct list of region cells and relative sizes
|
||||||
|
addRegion(primaryRegionParams, primarySubRegionParams, Vector2d.POSITIVE_XY)
|
||||||
|
|
||||||
|
for ((i, region) in secondaryRegions.withIndex()) {
|
||||||
|
addRegion(region, secondarySubRegions[i], secondaryRegionSize)
|
||||||
|
}
|
||||||
|
|
||||||
|
// construct boundaries based on normalized sizes
|
||||||
|
var nextBoundary = random.nextInt(0, worldSize.x)
|
||||||
|
layer.boundaries.add(nextBoundary)
|
||||||
|
|
||||||
|
for (v in relativeRegionSizes) {
|
||||||
|
nextBoundary += (worldSize.x * v / totalRelativeSize).roundToInt()
|
||||||
|
layer.boundaries.add(nextBoundary)
|
||||||
|
}
|
||||||
|
|
||||||
|
// wrap cells + boundaries
|
||||||
|
while (layer.boundaries.last() > worldSize.x) {
|
||||||
|
layer.cells.add(0, layer.cells.removeLast())
|
||||||
|
layer.boundaries.add(0, layer.boundaries.removeLast() - worldSize.x)
|
||||||
|
}
|
||||||
|
|
||||||
|
layer.cells.add(0, layer.cells.last())
|
||||||
|
val yRange = GlobalDefaults.worldTemplate.playerStartSearchYRange
|
||||||
|
var i = 0
|
||||||
|
var lastBoundary = 0
|
||||||
|
|
||||||
|
for (region in layer.cells) {
|
||||||
|
nextBoundary = if (i < layer.boundaries.size) layer.boundaries.getInt(i) else worldSize.x
|
||||||
|
|
||||||
|
if (region.blockBiome in spawnBiomes) {
|
||||||
|
playerStartSearchRegions.add(AABBi(
|
||||||
|
Vector2i(lastBoundary, (yBase - yRange).coerceAtLeast(0)),
|
||||||
|
Vector2i(nextBoundary, (yBase + yRange).coerceAtMost(worldSize.y)),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
lastBoundary = nextBoundary
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
|
||||||
|
layers.add(layer)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun finalize(skyColoring: RGBAColor) {
|
||||||
|
layers.sort()
|
||||||
|
|
||||||
|
for (biome in biomes) {
|
||||||
|
biome.parallax?.fadeToSkyColor(skyColoring)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,100 @@
|
|||||||
|
package ru.dbotthepony.kstarbound.defs.world
|
||||||
|
|
||||||
|
import com.google.gson.JsonNull
|
||||||
|
import com.google.gson.JsonObject
|
||||||
|
import ru.dbotthepony.kommons.gson.set
|
||||||
|
import ru.dbotthepony.kommons.util.Either
|
||||||
|
import ru.dbotthepony.kommons.vector.Vector2i
|
||||||
|
import ru.dbotthepony.kstarbound.Starbound
|
||||||
|
import ru.dbotthepony.kstarbound.fromJson
|
||||||
|
import ru.dbotthepony.kstarbound.json.builder.JsonFactory
|
||||||
|
import ru.dbotthepony.kstarbound.world.Universe
|
||||||
|
import ru.dbotthepony.kstarbound.world.UniversePos
|
||||||
|
import ru.dbotthepony.kstarbound.world.WorldGeometry
|
||||||
|
import kotlin.properties.Delegates
|
||||||
|
|
||||||
|
class WorldTemplate(val geometry: WorldGeometry) {
|
||||||
|
var seed: Long = 0L
|
||||||
|
private set
|
||||||
|
var worldName: String = ""
|
||||||
|
private set
|
||||||
|
var worldParameters: VisitableWorldParameters? = null
|
||||||
|
private set
|
||||||
|
var worldLayout: WorldLayout? = null
|
||||||
|
private set
|
||||||
|
var skyParameters: SkyParameters = SkyParameters()
|
||||||
|
private set
|
||||||
|
var celestialParameters: CelestialParameters? = null
|
||||||
|
private set
|
||||||
|
|
||||||
|
constructor(worldParameters: VisitableWorldParameters, skyParameters: SkyParameters, seed: Long) : this(WorldGeometry(worldParameters.worldSize, true, false)) {
|
||||||
|
this.seed = seed
|
||||||
|
this.skyParameters = skyParameters
|
||||||
|
this.worldLayout = worldParameters.createLayout(seed)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun determineName() {
|
||||||
|
if (celestialParameters != null) {
|
||||||
|
worldName = celestialParameters!!.name
|
||||||
|
} else if (worldParameters is FloatingDungeonWorldParameters) {
|
||||||
|
|
||||||
|
} else {
|
||||||
|
worldName = ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@JsonFactory
|
||||||
|
data class SerializedForm(
|
||||||
|
val celestialParameters: CelestialParameters? = null,
|
||||||
|
val worldParameters: VisitableWorldParameters? = null,
|
||||||
|
val skyParameters: SkyParameters = SkyParameters(),
|
||||||
|
val seed: Long = 0L,
|
||||||
|
val size: Either<WorldGeometry, Vector2i>,
|
||||||
|
val regionData: WorldLayout? = null,
|
||||||
|
//val customTerrainRegions:
|
||||||
|
)
|
||||||
|
|
||||||
|
fun toJson(isLegacy: Boolean): JsonObject {
|
||||||
|
val data = Starbound.gson.toJsonTree(SerializedForm(
|
||||||
|
celestialParameters, worldParameters, skyParameters, seed,
|
||||||
|
if (isLegacy) Either.right(geometry.size) else Either.left(geometry),
|
||||||
|
)) as JsonObject
|
||||||
|
|
||||||
|
data["regionData"] = worldLayout?.toJson(isLegacy) ?: JsonNull.INSTANCE
|
||||||
|
return data
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
suspend fun create(coordinate: UniversePos, universe: Universe): WorldTemplate {
|
||||||
|
val params = universe.parameters(coordinate) ?: throw IllegalArgumentException("$universe has nothing at $coordinate!")
|
||||||
|
val visitable = params.visitableParameters ?: throw IllegalArgumentException("$coordinate of $universe is not visitable")
|
||||||
|
|
||||||
|
val template = WorldTemplate(WorldGeometry(visitable.worldSize, true, false))
|
||||||
|
|
||||||
|
template.seed = params.seed
|
||||||
|
template.worldLayout = visitable.createLayout(params.seed)
|
||||||
|
template.skyParameters = SkyParameters.create(coordinate, universe)
|
||||||
|
template.celestialParameters = params
|
||||||
|
template.worldParameters = visitable
|
||||||
|
|
||||||
|
template.determineName()
|
||||||
|
|
||||||
|
return template
|
||||||
|
}
|
||||||
|
|
||||||
|
fun fromJson(data: JsonObject): WorldTemplate {
|
||||||
|
val load = Starbound.gson.fromJson(data, SerializedForm::class.java)
|
||||||
|
val template = WorldTemplate(load.size.map({ it }, { WorldGeometry(it, true, false) }))
|
||||||
|
|
||||||
|
template.celestialParameters = load.celestialParameters
|
||||||
|
template.worldParameters = load.worldParameters
|
||||||
|
template.skyParameters = load.skyParameters
|
||||||
|
template.seed = load.seed
|
||||||
|
template.worldLayout = load.regionData
|
||||||
|
|
||||||
|
template.determineName()
|
||||||
|
|
||||||
|
return template
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,8 @@
|
|||||||
|
package ru.dbotthepony.kstarbound.defs.world
|
||||||
|
|
||||||
|
import ru.dbotthepony.kstarbound.json.builder.JsonFactory
|
||||||
|
|
||||||
|
@JsonFactory
|
||||||
|
data class WorldTemplateConfig(
|
||||||
|
val playerStartSearchYRange: Int,
|
||||||
|
)
|
@ -0,0 +1,47 @@
|
|||||||
|
package ru.dbotthepony.kstarbound.defs.world.terrain
|
||||||
|
|
||||||
|
import com.google.gson.JsonObject
|
||||||
|
import ru.dbotthepony.kommons.gson.set
|
||||||
|
import ru.dbotthepony.kstarbound.Starbound
|
||||||
|
|
||||||
|
abstract class AbstractTerrainSelector<D : Any>(val name: String, val config: D, val parameters: TerrainSelectorParameters) {
|
||||||
|
// Returns a float signifying the "solid-ness" of a block, >= 0.0 should be
|
||||||
|
// considered solid, < 0.0 should be considered open space.
|
||||||
|
abstract operator fun get(x: Int, y: Int): Double
|
||||||
|
|
||||||
|
abstract val type: TerrainSelectorType
|
||||||
|
|
||||||
|
fun toJson(): JsonObject {
|
||||||
|
val result = JsonObject()
|
||||||
|
result["name"] = name
|
||||||
|
result["config"] = Starbound.gson.toJsonTree(config)
|
||||||
|
result["parameters"] = Starbound.gson.toJsonTree(parameters)
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun equals(other: Any?): Boolean {
|
||||||
|
if (this === other)
|
||||||
|
return true
|
||||||
|
|
||||||
|
if (other == null || this::class.java != other::class.java)
|
||||||
|
return false
|
||||||
|
|
||||||
|
other as AbstractTerrainSelector<*>
|
||||||
|
return name == other.name && config == other.config && parameters == other.parameters
|
||||||
|
}
|
||||||
|
|
||||||
|
private val hash by lazy {
|
||||||
|
var h = name.hashCode()
|
||||||
|
h = h * 31 + config.hashCode()
|
||||||
|
h = h * 31 + parameters.hashCode()
|
||||||
|
h
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun hashCode(): Int {
|
||||||
|
return hash
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun toString(): String {
|
||||||
|
return "${this::class.simpleName}[$name, config=$config, parameters=$parameters]"
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,241 @@
|
|||||||
|
package ru.dbotthepony.kstarbound.defs.world.terrain
|
||||||
|
|
||||||
|
import com.google.common.collect.ImmutableList
|
||||||
|
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.JsonPrimitive
|
||||||
|
import com.google.gson.JsonSyntaxException
|
||||||
|
import com.google.gson.TypeAdapter
|
||||||
|
import com.google.gson.TypeAdapterFactory
|
||||||
|
import com.google.gson.reflect.TypeToken
|
||||||
|
import com.google.gson.stream.JsonReader
|
||||||
|
import com.google.gson.stream.JsonWriter
|
||||||
|
import ru.dbotthepony.kommons.collect.filterNotNull
|
||||||
|
import ru.dbotthepony.kommons.gson.consumeNull
|
||||||
|
import ru.dbotthepony.kommons.gson.stream
|
||||||
|
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.tile.MaterialModifier
|
||||||
|
import ru.dbotthepony.kstarbound.defs.tile.TileDefinition
|
||||||
|
import ru.dbotthepony.kstarbound.defs.world.AmbientNoisesDefinition
|
||||||
|
import ru.dbotthepony.kstarbound.defs.world.BushVariant
|
||||||
|
import ru.dbotthepony.kstarbound.defs.world.GrassVariant
|
||||||
|
import ru.dbotthepony.kstarbound.defs.world.Parallax
|
||||||
|
import ru.dbotthepony.kstarbound.defs.world.TreeVariant
|
||||||
|
import ru.dbotthepony.kstarbound.json.builder.JsonFactory
|
||||||
|
import ru.dbotthepony.kstarbound.json.builder.JsonFlat
|
||||||
|
import ru.dbotthepony.kstarbound.json.builder.JsonImplementation
|
||||||
|
import ru.dbotthepony.kstarbound.json.listAdapter
|
||||||
|
import ru.dbotthepony.kstarbound.util.random.AbstractPerlinNoise
|
||||||
|
import java.util.stream.Stream
|
||||||
|
|
||||||
|
@JsonFactory
|
||||||
|
data class BiomePlaceables(
|
||||||
|
val grassMod: Registry.Entry<MaterialModifier>? = null,
|
||||||
|
val ceilingGrassMod: Registry.Entry<MaterialModifier>? = null,
|
||||||
|
val grassModDensity: Double = 0.0,
|
||||||
|
val ceilingGrassModDensity: Double = 0.0,
|
||||||
|
val items: ImmutableList<DistributionItem> = ImmutableList.of(),
|
||||||
|
) {
|
||||||
|
fun firstTreeVariant(): TreeVariant? {
|
||||||
|
return items.stream()
|
||||||
|
.flatMap { it.data.itemStream() }
|
||||||
|
.map { it as? Tree }
|
||||||
|
.filterNotNull()
|
||||||
|
.flatMap { it.trees.stream() }
|
||||||
|
.findAny()
|
||||||
|
.orElse(null)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ----------- ITEMS
|
||||||
|
@JsonFactory
|
||||||
|
data class DistributionItem(
|
||||||
|
val priority: Double = 0.0,
|
||||||
|
val variants: Int = 1,
|
||||||
|
val mode: BiomePlaceablesDefinition.Placement = BiomePlaceablesDefinition.Placement.FLOOR,
|
||||||
|
@JsonFlat
|
||||||
|
val data: DistributionData,
|
||||||
|
)
|
||||||
|
|
||||||
|
abstract class Item {
|
||||||
|
abstract val type: BiomePlacementItemType
|
||||||
|
|
||||||
|
abstract fun toJson(): JsonElement
|
||||||
|
|
||||||
|
companion object : TypeAdapterFactory {
|
||||||
|
override fun <T : Any> create(gson: Gson, type: TypeToken<T>): TypeAdapter<T>? {
|
||||||
|
if (Item::class.java.isAssignableFrom(type.rawType)) {
|
||||||
|
return object : TypeAdapter<Item>() {
|
||||||
|
private val arrays = gson.getAdapter(JsonArray::class.java)
|
||||||
|
private val grassVariant = gson.getAdapter(GrassVariant::class.java)
|
||||||
|
private val bushVariant = gson.getAdapter(BushVariant::class.java)
|
||||||
|
private val trees = gson.listAdapter<TreeVariant>()
|
||||||
|
private val objects = gson.getAdapter(PoolTypeToken)
|
||||||
|
|
||||||
|
override fun write(out: JsonWriter, value: Item?) {
|
||||||
|
if (value == null)
|
||||||
|
out.nullValue()
|
||||||
|
else {
|
||||||
|
out.beginArray()
|
||||||
|
|
||||||
|
when (value.type) {
|
||||||
|
BiomePlacementItemType.MICRO_DUNGEON -> out.value("microDungeon")
|
||||||
|
BiomePlacementItemType.TREASURE_BOX_SET -> out.value("treasureBoxSet")
|
||||||
|
BiomePlacementItemType.GRASS -> out.value("grass")
|
||||||
|
BiomePlacementItemType.BUSH -> out.value("bush")
|
||||||
|
BiomePlacementItemType.TREE -> out.value("treePair")
|
||||||
|
BiomePlacementItemType.OBJECT -> out.value("objectPool")
|
||||||
|
}
|
||||||
|
|
||||||
|
out.value(value.toJson())
|
||||||
|
out.endArray()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun read(`in`: JsonReader): Item? {
|
||||||
|
if (`in`.consumeNull())
|
||||||
|
return null
|
||||||
|
|
||||||
|
`in`.beginArray()
|
||||||
|
|
||||||
|
// sucks that this has to be done manually
|
||||||
|
// Also I salute to whoever decided to give different names
|
||||||
|
// and different comparison rules for data in *.biome files
|
||||||
|
// and world storage data at Chucklefish.
|
||||||
|
// Truly our hero here.
|
||||||
|
val obj = when (val type = `in`.nextString()) {
|
||||||
|
"treasureBoxSet" -> TreasureBox(`in`.nextString())
|
||||||
|
"microDungeon" -> MicroDungeon(arrays.read(`in`).stream().map { it.asString }.collect(ImmutableSet.toImmutableSet()))
|
||||||
|
"grass" -> Grass(grassVariant.read(`in`))
|
||||||
|
"bush" -> Bush(bushVariant.read(`in`))
|
||||||
|
"tree" -> Tree(trees.read(`in`))
|
||||||
|
"objectPool" -> Object(objects.read(`in`))
|
||||||
|
else -> throw JsonSyntaxException("Unknown biome placement item $type")
|
||||||
|
}
|
||||||
|
|
||||||
|
`in`.endArray()
|
||||||
|
return obj
|
||||||
|
}
|
||||||
|
} as TypeAdapter<T>
|
||||||
|
}
|
||||||
|
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
data class MicroDungeon(val microdungeons: ImmutableSet<String> = ImmutableSet.of()) : Item() {
|
||||||
|
override val type: BiomePlacementItemType
|
||||||
|
get() = BiomePlacementItemType.MICRO_DUNGEON
|
||||||
|
|
||||||
|
override fun toJson(): JsonElement {
|
||||||
|
return JsonArray().also { j ->
|
||||||
|
microdungeons.forEach { j.add(JsonPrimitive(it)) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
data class TreasureBox(val pool: String) : Item() {
|
||||||
|
override val type: BiomePlacementItemType
|
||||||
|
get() = BiomePlacementItemType.TREASURE_BOX_SET
|
||||||
|
|
||||||
|
override fun toJson(): JsonElement {
|
||||||
|
return JsonPrimitive(pool)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
data class Grass(val value: GrassVariant) : Item() {
|
||||||
|
override val type: BiomePlacementItemType
|
||||||
|
get() = BiomePlacementItemType.GRASS
|
||||||
|
|
||||||
|
override fun toJson(): JsonElement {
|
||||||
|
return Starbound.gson.toJsonTree(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
data class Bush(val value: BushVariant) : Item() {
|
||||||
|
override val type: BiomePlacementItemType
|
||||||
|
get() = BiomePlacementItemType.BUSH
|
||||||
|
|
||||||
|
override fun toJson(): JsonElement {
|
||||||
|
return Starbound.gson.toJsonTree(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
data class Tree(val trees: ImmutableList<TreeVariant>) : Item() {
|
||||||
|
override val type: BiomePlacementItemType
|
||||||
|
get() = BiomePlacementItemType.TREE
|
||||||
|
|
||||||
|
override fun toJson(): JsonElement {
|
||||||
|
return Starbound.gson.toJsonTree(trees, TypeToken.getParameterized(ImmutableList::class.java, TreeVariant::class.java).type)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private object PoolTypeToken : TypeToken<WeightedList<Pair<String, JsonElement>>>()
|
||||||
|
|
||||||
|
// This structure sucks, but at least it allows unique parameters per
|
||||||
|
// each object (lmao, whos gonna write world json by hand anyway????
|
||||||
|
// considering this is world generation data.)
|
||||||
|
data class Object(val pool: WeightedList<Pair<String, JsonElement>>) : Item() {
|
||||||
|
override val type: BiomePlacementItemType
|
||||||
|
get() = BiomePlacementItemType.OBJECT
|
||||||
|
|
||||||
|
override fun toJson(): JsonElement {
|
||||||
|
return Starbound.gson.toJsonTree(pool, PoolTypeToken.type)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// -------- 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 Biome(
|
||||||
|
val hueShift: Double = 0.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 musicTrack: AmbientNoisesDefinition? = null,
|
||||||
|
val ambientNoises: AmbientNoisesDefinition? = null,
|
||||||
|
val surfacePlaceables: BiomePlaceables = BiomePlaceables(),
|
||||||
|
val undergroundPlaceables: BiomePlaceables = BiomePlaceables(),
|
||||||
|
val parallax: Parallax? = null,
|
||||||
|
)
|
@ -0,0 +1,470 @@
|
|||||||
|
package ru.dbotthepony.kstarbound.defs.world.terrain
|
||||||
|
|
||||||
|
import com.google.common.collect.ImmutableList
|
||||||
|
import com.google.common.collect.ImmutableSet
|
||||||
|
import com.google.gson.JsonElement
|
||||||
|
import com.google.gson.JsonObject
|
||||||
|
import com.google.gson.reflect.TypeToken
|
||||||
|
import com.google.gson.stream.JsonWriter
|
||||||
|
import ru.dbotthepony.kommons.collect.filterNotNull
|
||||||
|
import ru.dbotthepony.kommons.vector.Vector2i
|
||||||
|
import ru.dbotthepony.kstarbound.Registries
|
||||||
|
import ru.dbotthepony.kstarbound.Registry
|
||||||
|
import ru.dbotthepony.kstarbound.Starbound
|
||||||
|
import ru.dbotthepony.kstarbound.collect.WeightedList
|
||||||
|
import ru.dbotthepony.kstarbound.defs.AssetReference
|
||||||
|
import ru.dbotthepony.kstarbound.defs.JsonConfigFunction
|
||||||
|
import ru.dbotthepony.kstarbound.defs.PerlinNoiseParameters
|
||||||
|
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.BushVariant
|
||||||
|
import ru.dbotthepony.kstarbound.defs.world.GrassVariant
|
||||||
|
import ru.dbotthepony.kstarbound.defs.world.Parallax
|
||||||
|
import ru.dbotthepony.kstarbound.defs.world.SkyColoring
|
||||||
|
import ru.dbotthepony.kstarbound.defs.world.TreeVariant
|
||||||
|
import ru.dbotthepony.kstarbound.json.builder.DispatchingAdapter
|
||||||
|
import ru.dbotthepony.kstarbound.json.builder.IStringSerializable
|
||||||
|
import ru.dbotthepony.kstarbound.json.builder.JsonFactory
|
||||||
|
import ru.dbotthepony.kstarbound.json.builder.JsonFlat
|
||||||
|
import ru.dbotthepony.kstarbound.json.builder.JsonSingleton
|
||||||
|
import ru.dbotthepony.kstarbound.json.pairListAdapter
|
||||||
|
import ru.dbotthepony.kstarbound.util.random.AbstractPerlinNoise
|
||||||
|
import ru.dbotthepony.kstarbound.util.random.nextRange
|
||||||
|
import ru.dbotthepony.kstarbound.util.random.random
|
||||||
|
import java.util.random.RandomGenerator
|
||||||
|
import java.util.stream.IntStream
|
||||||
|
|
||||||
|
enum class BiomePlacementDistributionType(
|
||||||
|
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));
|
||||||
|
|
||||||
|
override fun match(name: String): Boolean {
|
||||||
|
return name.lowercase() == jsonName
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun write(out: JsonWriter) {
|
||||||
|
out.value(jsonName)
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
val DEFINITION_ADAPTER = DispatchingAdapter("type", { type }, { def }, entries)
|
||||||
|
val DATA_ADAPTER = DispatchingAdapter("type", { type }, { data }, entries)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
enum class BiomePlacementItemType(
|
||||||
|
val jsonName: String,
|
||||||
|
val def: TypeToken<out BiomePlaceablesDefinition.DistributionItemData>,
|
||||||
|
val data: TypeToken<out BiomePlaceables.Item>,
|
||||||
|
) : IStringSerializable {
|
||||||
|
MICRO_DUNGEON("microdungeon", TypeToken.get(BiomePlaceablesDefinition.MicroDungeon::class.java), TypeToken.get(BiomePlaceables.MicroDungeon::class.java)),
|
||||||
|
TREASURE_BOX_SET("treasurebox", TypeToken.get(BiomePlaceablesDefinition.TreasureBox::class.java), TypeToken.get(BiomePlaceables.TreasureBox::class.java)),
|
||||||
|
GRASS("grass", TypeToken.get(BiomePlaceablesDefinition.Grass::class.java), TypeToken.get(BiomePlaceables.Grass::class.java)),
|
||||||
|
BUSH("bush", TypeToken.get(BiomePlaceablesDefinition.Bush::class.java), TypeToken.get(BiomePlaceables.Bush::class.java)),
|
||||||
|
TREE("tree", TypeToken.get(BiomePlaceablesDefinition.Tree::class.java), TypeToken.get(BiomePlaceables.Tree::class.java)),
|
||||||
|
OBJECT("object", TypeToken.get(BiomePlaceablesDefinition.Object::class.java), TypeToken.get(BiomePlaceables.Object::class.java)),
|
||||||
|
;
|
||||||
|
|
||||||
|
override fun match(name: String): Boolean {
|
||||||
|
return name.lowercase() == jsonName
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun write(out: JsonWriter) {
|
||||||
|
out.value(jsonName)
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
val DEFINITION_ADAPTER = DispatchingAdapter("type", { type }, { def }, entries)
|
||||||
|
val DATA_ADAPTER = DispatchingAdapter("type", { type }, { data }, entries)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@JsonFactory
|
||||||
|
data class BiomePlaceablesDefinition(
|
||||||
|
val grassMod: ImmutableList<Registry.Ref<MaterialModifier>> = ImmutableList.of(),
|
||||||
|
val grassModDensity: Double = 0.0,
|
||||||
|
val ceilingGrassMod: ImmutableList<Registry.Ref<MaterialModifier>> = ImmutableList.of(),
|
||||||
|
val ceilingGrassModDensity: Double = 0.0,
|
||||||
|
val items: ImmutableList<DistributionItem> = ImmutableList.of(),
|
||||||
|
) {
|
||||||
|
enum class Placement {
|
||||||
|
FLOOR, CEILING, BACKGROUND, OCEAN;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ----------- ITEMS
|
||||||
|
@JsonFactory
|
||||||
|
data class DistributionItem(
|
||||||
|
val priority: Double = 0.0,
|
||||||
|
val variants: Int = 1,
|
||||||
|
val mode: Placement = Placement.FLOOR,
|
||||||
|
val distribution: AssetReference<DistributionData>,
|
||||||
|
@JsonFlat
|
||||||
|
val data: DistributionItemData,
|
||||||
|
) {
|
||||||
|
init {
|
||||||
|
checkNotNull(distribution.value) { "Distribution data is missing" }
|
||||||
|
}
|
||||||
|
|
||||||
|
fun create(biome: BiomeDefinition.CreationParams): BiomePlaceables.DistributionItem {
|
||||||
|
return BiomePlaceables.DistributionItem(
|
||||||
|
priority = priority,
|
||||||
|
variants = variants,
|
||||||
|
mode = mode,
|
||||||
|
data = distribution.value!!.create(this, biome),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract class DistributionItemData {
|
||||||
|
abstract val type: BiomePlacementItemType
|
||||||
|
abstract fun create(biome: BiomeDefinition.CreationParams): BiomePlaceables.Item
|
||||||
|
}
|
||||||
|
|
||||||
|
@JsonFactory
|
||||||
|
data class MicroDungeon(val microdungeons: ImmutableSet<String> = ImmutableSet.of()) : DistributionItemData() {
|
||||||
|
override val type: BiomePlacementItemType
|
||||||
|
get() = BiomePlacementItemType.MICRO_DUNGEON
|
||||||
|
|
||||||
|
override fun create(biome: BiomeDefinition.CreationParams): BiomePlaceables.Item {
|
||||||
|
return BiomePlaceables.MicroDungeon(microdungeons)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@JsonFactory
|
||||||
|
data class TreasureBox(val treasureBoxSets: ImmutableSet<String> = ImmutableSet.of()) : DistributionItemData() {
|
||||||
|
override val type: BiomePlacementItemType
|
||||||
|
get() = BiomePlacementItemType.MICRO_DUNGEON
|
||||||
|
|
||||||
|
override fun create(biome: BiomeDefinition.CreationParams): BiomePlaceables.Item {
|
||||||
|
return BiomePlaceables.TreasureBox(treasureBoxSets.random(biome.random))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@JsonFactory
|
||||||
|
data class Grass(val grasses: ImmutableSet<Registry.Ref<GrassVariant.Data>> = ImmutableSet.of()) : DistributionItemData() {
|
||||||
|
override val type: BiomePlacementItemType
|
||||||
|
get() = BiomePlacementItemType.GRASS
|
||||||
|
|
||||||
|
override fun create(biome: BiomeDefinition.CreationParams): BiomePlaceables.Item {
|
||||||
|
val valid = grasses.stream().map { it.entry }.filterNotNull().toList()
|
||||||
|
|
||||||
|
if (valid.isEmpty()) {
|
||||||
|
throw NoSuchElementException("None of grass variants are valid (candidates: $grasses)")
|
||||||
|
}
|
||||||
|
|
||||||
|
return BiomePlaceables.Grass(GrassVariant.create(valid.random(biome.random), biome.hueShift))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@JsonFactory
|
||||||
|
data class Tree(
|
||||||
|
val treeStemList: ImmutableList<Registry.Ref<TreeVariant.StemData>> = ImmutableList.of(),
|
||||||
|
val treeFoliageList: ImmutableList<Registry.Ref<TreeVariant.FoliageData>> = ImmutableList.of(),
|
||||||
|
val treeStemHueShiftMax: Double = 0.0,
|
||||||
|
val treeFoliageHueShiftMax: Double = 0.0,
|
||||||
|
val variantsRange: Vector2i = Vector2i(1, 1),
|
||||||
|
val subVariantsRange: Vector2i = Vector2i(2, 2),
|
||||||
|
val sameStemHueShift: Boolean = true,
|
||||||
|
val sameFoliageHueShift: Boolean = false,
|
||||||
|
) : DistributionItemData() {
|
||||||
|
override val type: BiomePlacementItemType
|
||||||
|
get() = BiomePlacementItemType.GRASS
|
||||||
|
|
||||||
|
override fun create(biome: BiomeDefinition.CreationParams): BiomePlaceables.Item {
|
||||||
|
val validTreeStems = treeStemList.stream().map { it.entry }.filterNotNull().toList()
|
||||||
|
val validFoliage = treeFoliageList.stream().filter { it.key.left().isBlank() || it.entry != null }.toList()
|
||||||
|
|
||||||
|
if (validTreeStems.isEmpty()) {
|
||||||
|
throw NoSuchElementException("None of tree stems variants are valid (candidates: $treeStemList)")
|
||||||
|
}
|
||||||
|
|
||||||
|
if (validFoliage.isEmpty()) {
|
||||||
|
throw NoSuchElementException("None of tree foliage variants are valid (candidates: $treeFoliageList)")
|
||||||
|
}
|
||||||
|
|
||||||
|
val pairs = ArrayList<Pair<Registry.Entry<TreeVariant.StemData>, Registry.Entry<TreeVariant.FoliageData>?>>()
|
||||||
|
|
||||||
|
for (stem in validTreeStems) {
|
||||||
|
for (foliage in validFoliage) {
|
||||||
|
if (foliage.entry == null || stem.value.shape == foliage.value!!.shape) {
|
||||||
|
pairs.add(stem to foliage?.entry)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pairs.isEmpty()) {
|
||||||
|
throw NoSuchElementException("Out of all possible combinations of tree stems and foliage, none match by shape (stems candidates: $validTreeStems; foliage candidates: $validFoliage)")
|
||||||
|
}
|
||||||
|
|
||||||
|
val treeVariants = biome.random.nextRange(variantsRange)
|
||||||
|
|
||||||
|
if (treeVariants <= 0) {
|
||||||
|
return BiomePlaceables.Tree(ImmutableList.of())
|
||||||
|
}
|
||||||
|
|
||||||
|
val trees = ArrayList<TreeVariant>()
|
||||||
|
|
||||||
|
for (i in 0 until treeVariants) {
|
||||||
|
val subTreeVariants = biome.random.nextRange(subVariantsRange)
|
||||||
|
if (subTreeVariants <= 0) continue
|
||||||
|
val (stem, foliage) = pairs.random(biome.random)
|
||||||
|
|
||||||
|
val treeStemHueShift = treeStemHueShiftMax * biome.random.nextDouble(-1.0, 1.0)
|
||||||
|
val treeFoliageHueShift = treeFoliageHueShiftMax * biome.random.nextDouble(-1.0, 1.0)
|
||||||
|
|
||||||
|
if (foliage == null) {
|
||||||
|
for (i2 in 0 until subTreeVariants) {
|
||||||
|
trees.add(TreeVariant.create(stem, if (sameStemHueShift) treeStemHueShift else treeStemHueShiftMax * biome.random.nextDouble(-1.0, 1.0)))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for (i2 in 0 until subTreeVariants) {
|
||||||
|
trees.add(TreeVariant.create(
|
||||||
|
stem, if (sameStemHueShift) treeStemHueShift else treeStemHueShiftMax * biome.random.nextDouble(-1.0, 1.0),
|
||||||
|
foliage, if (sameFoliageHueShift) treeFoliageHueShift else treeFoliageHueShiftMax * biome.random.nextDouble(-1.0, 1.0)
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return BiomePlaceables.Tree(ImmutableList.copyOf(trees))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@JsonFactory
|
||||||
|
data class BushData(
|
||||||
|
val name: Registry.Ref<BushVariant.Data>,
|
||||||
|
val baseHueShiftMax: Double = 0.0,
|
||||||
|
val modHueShiftMax: Double = 0.0,
|
||||||
|
)
|
||||||
|
|
||||||
|
@JsonFactory
|
||||||
|
data class Bush(val bushes: ImmutableSet<BushData> = ImmutableSet.of()) : DistributionItemData() {
|
||||||
|
override val type: BiomePlacementItemType
|
||||||
|
get() = BiomePlacementItemType.BUSH
|
||||||
|
|
||||||
|
override fun create(biome: BiomeDefinition.CreationParams): BiomePlaceables.Item {
|
||||||
|
val valid = bushes.stream().filter { it.name.entry != null }.toList()
|
||||||
|
|
||||||
|
if (valid.isEmpty()) {
|
||||||
|
throw NoSuchElementException("None of bush variants are valid (candidates: $bushes)")
|
||||||
|
}
|
||||||
|
|
||||||
|
val (name, baseHueShiftMax, modHueShiftMax) = valid.random(biome.random)
|
||||||
|
val data = name.entry!!.value
|
||||||
|
val mod = if (data.mods.isNotEmpty()) data.mods.random(biome.random) else ""
|
||||||
|
|
||||||
|
return BiomePlaceables.Bush(BushVariant.create(name.entry!!, biome.random.nextDouble(-1.0, 1.0) * baseHueShiftMax, mod, biome.random.nextDouble(-1.0 * 1.0) * modHueShiftMax))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@JsonFactory
|
||||||
|
data class ObjectPool(val pool: ImmutableList<Pair<Double, String>> = ImmutableList.of(), val parameters: JsonElement = JsonObject())
|
||||||
|
|
||||||
|
@JsonFactory
|
||||||
|
data class Object(val objectSets: ImmutableList<ObjectPool>) : DistributionItemData() {
|
||||||
|
override val type: BiomePlacementItemType
|
||||||
|
get() = BiomePlacementItemType.OBJECT
|
||||||
|
|
||||||
|
override fun create(biome: BiomeDefinition.CreationParams): BiomePlaceables.Item {
|
||||||
|
if (objectSets.isEmpty()) {
|
||||||
|
return BiomePlaceables.Object(WeightedList())
|
||||||
|
}
|
||||||
|
|
||||||
|
val rand = objectSets.random(biome.random)
|
||||||
|
return BiomePlaceables.Object(WeightedList(rand.pool.stream().map { it.first to (it.second to rand.parameters) }.collect(ImmutableList.toImmutableList())))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// --------------- DISTRIBUTION
|
||||||
|
abstract class DistributionData {
|
||||||
|
abstract val type: BiomePlacementDistributionType
|
||||||
|
abstract fun create(self: DistributionItem, biome: BiomeDefinition.CreationParams): BiomePlaceables.DistributionData
|
||||||
|
}
|
||||||
|
|
||||||
|
@JsonSingleton
|
||||||
|
object RandomDistribution : DistributionData() {
|
||||||
|
override val type: BiomePlacementDistributionType
|
||||||
|
get() = BiomePlacementDistributionType.RANDOM
|
||||||
|
|
||||||
|
override fun create(
|
||||||
|
self: DistributionItem,
|
||||||
|
biome: BiomeDefinition.CreationParams
|
||||||
|
): BiomePlaceables.DistributionData {
|
||||||
|
return BiomePlaceables.RandomDistribution(
|
||||||
|
blockSeed = biome.random.nextLong(),
|
||||||
|
randomItems = IntStream.range(0, self.variants)
|
||||||
|
.mapToObj { self.data.create(biome) }
|
||||||
|
.collect(ImmutableList.toImmutableList())
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@JsonFactory
|
||||||
|
data class PeriodicDistribution(
|
||||||
|
val modulus: Int = 1,
|
||||||
|
val octaves: Int = 1,
|
||||||
|
val alpha: Double = 2.0,
|
||||||
|
val beta: Double = 2.0,
|
||||||
|
val modulusVariance: Double = 0.0,
|
||||||
|
val densityPeriod: Double = 10.0,
|
||||||
|
val densityOffset: Double = 2.0,
|
||||||
|
val typePeriod: Double = 10.0,
|
||||||
|
val noiseType: PerlinNoiseParameters.Type = PerlinNoiseParameters.Type.PERLIN,
|
||||||
|
val noiseScale: Int = PerlinNoiseParameters.DEFAULT_SCALE,
|
||||||
|
) : DistributionData() {
|
||||||
|
override val type: BiomePlacementDistributionType
|
||||||
|
get() = BiomePlacementDistributionType.PERIODIC
|
||||||
|
|
||||||
|
override fun create(
|
||||||
|
self: DistributionItem,
|
||||||
|
biome: BiomeDefinition.CreationParams
|
||||||
|
): BiomePlaceables.DistributionData {
|
||||||
|
val modulusOffset = if (modulus == 0) 0 else biome.random.nextInt(-modulus, modulus)
|
||||||
|
|
||||||
|
return BiomePlaceables.PeriodicDistribution(
|
||||||
|
modulus = modulus,
|
||||||
|
modulusOffset = modulusOffset,
|
||||||
|
|
||||||
|
densityFunction = AbstractPerlinNoise.of(
|
||||||
|
PerlinNoiseParameters(
|
||||||
|
type = noiseType,
|
||||||
|
scale = noiseScale,
|
||||||
|
octaves = octaves,
|
||||||
|
alpha = alpha,
|
||||||
|
beta = beta,
|
||||||
|
|
||||||
|
frequency = 1.0 / densityPeriod,
|
||||||
|
amplitude = 1.0,
|
||||||
|
bias = densityOffset,
|
||||||
|
seed = biome.random.nextLong()
|
||||||
|
)
|
||||||
|
),
|
||||||
|
|
||||||
|
modulusDistortion = AbstractPerlinNoise.of(
|
||||||
|
PerlinNoiseParameters(
|
||||||
|
type = noiseType,
|
||||||
|
scale = noiseScale,
|
||||||
|
octaves = octaves,
|
||||||
|
alpha = alpha,
|
||||||
|
beta = beta,
|
||||||
|
|
||||||
|
frequency = 1.0 / modulus,
|
||||||
|
amplitude = modulusVariance,
|
||||||
|
bias = modulusVariance * 2.0,
|
||||||
|
seed = biome.random.nextLong()
|
||||||
|
)
|
||||||
|
),
|
||||||
|
|
||||||
|
weightedItems = IntStream.range(0, self.variants)
|
||||||
|
.mapToObj { self.data.create(biome) }
|
||||||
|
.map { it to AbstractPerlinNoise.of(
|
||||||
|
PerlinNoiseParameters(
|
||||||
|
type = noiseType,
|
||||||
|
scale = noiseScale,
|
||||||
|
octaves = octaves,
|
||||||
|
alpha = alpha,
|
||||||
|
beta = beta,
|
||||||
|
|
||||||
|
frequency = 1.0 / typePeriod,
|
||||||
|
amplitude = 1.0,
|
||||||
|
bias = 0.0,
|
||||||
|
seed = biome.random.nextLong(),
|
||||||
|
)
|
||||||
|
) }
|
||||||
|
.collect(ImmutableList.toImmutableList())
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun create(biome: BiomeDefinition.CreationParams): BiomePlaceables {
|
||||||
|
return BiomePlaceables(
|
||||||
|
grassMod = grassMod.random(biome.random) { null }?.entry,
|
||||||
|
ceilingGrassMod = ceilingGrassMod.random(biome.random) { null }?.entry,
|
||||||
|
grassModDensity = grassModDensity,
|
||||||
|
ceilingGrassModDensity = ceilingGrassModDensity,
|
||||||
|
items = items.stream()
|
||||||
|
.map { it.create(biome) }
|
||||||
|
.collect(ImmutableList.toImmutableList())
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@JsonFactory
|
||||||
|
data class BiomeDefinition(
|
||||||
|
val airless: Boolean = false,
|
||||||
|
val name: String,
|
||||||
|
val statusEffects: ImmutableSet<String> = ImmutableSet.of(),
|
||||||
|
val weather: ImmutableList<Pair<Double, ImmutableList<AssetReference<WeightedList<String>>>>> = ImmutableList.of(), // binned reference to other assets
|
||||||
|
val hueShiftOptions: ImmutableList<Double> = ImmutableList.of(),
|
||||||
|
val skyOptions: ImmutableList<SkyColoring> = ImmutableList.of(),
|
||||||
|
val description: String = "...",
|
||||||
|
val mainBlock: Registry.Ref<TileDefinition>? = null,
|
||||||
|
val subBlocks: ImmutableList<Registry.Ref<TileDefinition>>? = null,
|
||||||
|
val ores: Registry.Ref<JsonConfigFunction>? = null,
|
||||||
|
val musicTrack: AmbientNoisesDefinition? = null,
|
||||||
|
val ambientNoises: AmbientNoisesDefinition? = null,
|
||||||
|
val surfacePlaceables: BiomePlaceablesDefinition = BiomePlaceablesDefinition(),
|
||||||
|
val undergroundPlaceables: BiomePlaceablesDefinition = BiomePlaceablesDefinition(),
|
||||||
|
val parallax: AssetReference<Parallax.Data>? = null,
|
||||||
|
) {
|
||||||
|
data class CreationParams(
|
||||||
|
val hueShift: Double,
|
||||||
|
val random: RandomGenerator
|
||||||
|
)
|
||||||
|
|
||||||
|
fun skyColoring(random: RandomGenerator): SkyColoring {
|
||||||
|
if (skyOptions.isEmpty())
|
||||||
|
return SkyColoring.BLACK
|
||||||
|
|
||||||
|
return skyOptions.random(random)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun hueShift(random: RandomGenerator): Double {
|
||||||
|
if (hueShiftOptions.isEmpty())
|
||||||
|
return 0.0
|
||||||
|
|
||||||
|
return hueShiftOptions.random(random)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun create(random: RandomGenerator, verticalMidPoint: Int, threatLevel: Double): Biome {
|
||||||
|
val hueShift = hueShift(random)
|
||||||
|
val data = CreationParams(hueShift = hueShift, random = random)
|
||||||
|
val surfacePlaceables = surfacePlaceables.create(data)
|
||||||
|
val undergroundPlaceables = undergroundPlaceables.create(data)
|
||||||
|
|
||||||
|
return Biome(
|
||||||
|
baseName = name,
|
||||||
|
description = description,
|
||||||
|
hueShift = hueShift,
|
||||||
|
|
||||||
|
mainBlock = mainBlock?.entry,
|
||||||
|
subBlocks = subBlocks?.stream()?.map { it.entry }?.filterNotNull()?.collect(ImmutableList.toImmutableList()) ?: ImmutableList.of(),
|
||||||
|
|
||||||
|
musicTrack = musicTrack,
|
||||||
|
ambientNoises = ambientNoises,
|
||||||
|
surfacePlaceables = surfacePlaceables,
|
||||||
|
undergroundPlaceables = undergroundPlaceables,
|
||||||
|
|
||||||
|
parallax = parallax?.value?.create(random, verticalMidPoint.toDouble(), hueShift, surfacePlaceables.firstTreeVariant()),
|
||||||
|
|
||||||
|
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 }
|
||||||
|
.collect(ImmutableList.toImmutableList())
|
||||||
|
}?.orElse(ImmutableList.of()) ?: ImmutableList.of()) as ImmutableList<Pair<Registry.Entry<MaterialModifier>, Double>>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private val oresAdapter by lazy {
|
||||||
|
Starbound.gson.pairListAdapter<String, Double>()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,15 @@
|
|||||||
|
package ru.dbotthepony.kstarbound.defs.world.terrain
|
||||||
|
|
||||||
|
import ru.dbotthepony.kstarbound.json.builder.JsonFactory
|
||||||
|
|
||||||
|
class ConstantTerrainSelector(name: String, data: Data, parameters: TerrainSelectorParameters) : AbstractTerrainSelector<ConstantTerrainSelector.Data>(name, data, parameters) {
|
||||||
|
@JsonFactory
|
||||||
|
data class Data(val value: Double)
|
||||||
|
|
||||||
|
override val type: TerrainSelectorType
|
||||||
|
get() = TerrainSelectorType.CONSTANT
|
||||||
|
|
||||||
|
override fun get(x: Int, y: Int): Double {
|
||||||
|
return config.value
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,116 @@
|
|||||||
|
package ru.dbotthepony.kstarbound.defs.world.terrain
|
||||||
|
|
||||||
|
import com.google.gson.JsonObject
|
||||||
|
import ru.dbotthepony.kommons.vector.Vector2d
|
||||||
|
import ru.dbotthepony.kstarbound.defs.PerlinNoiseParameters
|
||||||
|
import ru.dbotthepony.kstarbound.json.builder.JsonFactory
|
||||||
|
import ru.dbotthepony.kstarbound.util.random.AbstractPerlinNoise
|
||||||
|
import kotlin.math.roundToInt
|
||||||
|
|
||||||
|
class DisplacementTerrainSelector(name: String, data: Data, parameters: TerrainSelectorParameters) : AbstractTerrainSelector<DisplacementTerrainSelector.Data>(name, data, parameters) {
|
||||||
|
@JsonFactory
|
||||||
|
data class Data(
|
||||||
|
val xType: PerlinNoiseParameters.Type,
|
||||||
|
val xScale: Int = 512,
|
||||||
|
val xOctaves: Int,
|
||||||
|
val xFreq: Double,
|
||||||
|
val xAmp: Double,
|
||||||
|
val xBias: Double = 0.0,
|
||||||
|
val xAlpha: Double = 2.0,
|
||||||
|
val xBeta: Double = 2.0,
|
||||||
|
|
||||||
|
val yType: PerlinNoiseParameters.Type,
|
||||||
|
val yScale: Int = 512,
|
||||||
|
val yOctaves: Int,
|
||||||
|
val yFreq: Double,
|
||||||
|
val yAmp: Double,
|
||||||
|
val yBias: Double = 0.0,
|
||||||
|
val yAlpha: Double = 2.0,
|
||||||
|
val yBeta: Double = 2.0,
|
||||||
|
|
||||||
|
val xXInfluence: Double = 1.0,
|
||||||
|
val xYInfluence: Double = 1.0,
|
||||||
|
val yXInfluence: Double = 1.0,
|
||||||
|
val yYInfluence: Double = 1.0,
|
||||||
|
|
||||||
|
val yClamp: Vector2d? = null,
|
||||||
|
val yClampSmoothing: Double = 0.0,
|
||||||
|
|
||||||
|
val xClamp: Vector2d? = null,
|
||||||
|
val xClampSmoothing: Double = 0.0,
|
||||||
|
|
||||||
|
val source: JsonObject,
|
||||||
|
)
|
||||||
|
|
||||||
|
val xFn: AbstractPerlinNoise
|
||||||
|
val yFn: AbstractPerlinNoise
|
||||||
|
val source: AbstractTerrainSelector<*>
|
||||||
|
|
||||||
|
init {
|
||||||
|
// This allows to have multiple nested displacement selectors with different seeds
|
||||||
|
// original engine isn't capable of this because nested selectors will have the same seed
|
||||||
|
val parameters = parameters.withRandom()
|
||||||
|
val random = parameters.random()
|
||||||
|
|
||||||
|
xFn = AbstractPerlinNoise.of(PerlinNoiseParameters(
|
||||||
|
type = data.xType,
|
||||||
|
scale = data.xScale,
|
||||||
|
octaves = data.xOctaves,
|
||||||
|
frequency = data.xFreq,
|
||||||
|
amplitude = data.xAmp,
|
||||||
|
bias = data.xBias,
|
||||||
|
alpha = data.xAlpha,
|
||||||
|
beta = data.xBeta,
|
||||||
|
))
|
||||||
|
|
||||||
|
yFn = AbstractPerlinNoise.of(PerlinNoiseParameters(
|
||||||
|
type = data.yType,
|
||||||
|
scale = data.yScale,
|
||||||
|
octaves = data.yOctaves,
|
||||||
|
frequency = data.yFreq,
|
||||||
|
amplitude = data.yAmp,
|
||||||
|
bias = data.yBias,
|
||||||
|
alpha = data.yAlpha,
|
||||||
|
beta = data.yBeta,
|
||||||
|
))
|
||||||
|
|
||||||
|
xFn.init(random.nextLong())
|
||||||
|
yFn.init(random.nextLong())
|
||||||
|
source = TerrainSelectorType.create(data.source, parameters)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun get(x: Int, y: Int): Double {
|
||||||
|
return source[clampX(xFn[x * config.xXInfluence, y * config.xYInfluence]).roundToInt(), clampY(yFn[x * config.yXInfluence, y * config.yYInfluence]).roundToInt()]
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun clampX(v: Double): Double {
|
||||||
|
if (config.xClamp == null)
|
||||||
|
return v
|
||||||
|
|
||||||
|
if (config.xClampSmoothing == 0.0)
|
||||||
|
return v.coerceIn(config.xClamp.x, config.xClamp.y)
|
||||||
|
|
||||||
|
return 0.2 * ((v - config.xClampSmoothing).coerceIn(config.xClamp.x, config.xClamp.y)
|
||||||
|
+ (v - 0.5 * config.xClampSmoothing).coerceIn(config.xClamp.x, config.xClamp.y)
|
||||||
|
+ (v).coerceIn(config.xClamp.x, config.xClamp.y)
|
||||||
|
+ (v + 0.5 * config.xClampSmoothing).coerceIn(config.xClamp.x, config.xClamp.y)
|
||||||
|
+ (v + config.xClampSmoothing).coerceIn(config.xClamp.x, config.xClamp.y))
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun clampY(v: Double): Double {
|
||||||
|
if (config.yClamp == null)
|
||||||
|
return v
|
||||||
|
|
||||||
|
if (config.xClampSmoothing == 0.0)
|
||||||
|
return v.coerceIn(config.yClamp.x, config.yClamp.y)
|
||||||
|
|
||||||
|
return 0.2 * ((v - config.yClampSmoothing).coerceIn(config.yClamp.x, config.yClamp.y)
|
||||||
|
+ (v - 0.5 * config.yClampSmoothing).coerceIn(config.yClamp.x, config.yClamp.y)
|
||||||
|
+ (v).coerceIn(config.yClamp.x, config.yClamp.y)
|
||||||
|
+ (v + 0.5 * config.yClampSmoothing).coerceIn(config.yClamp.x, config.yClamp.y)
|
||||||
|
+ (v + config.yClampSmoothing).coerceIn(config.yClamp.x, config.yClamp.y))
|
||||||
|
}
|
||||||
|
|
||||||
|
override val type: TerrainSelectorType
|
||||||
|
get() = TerrainSelectorType.DISPLACEMENT
|
||||||
|
}
|
@ -0,0 +1,7 @@
|
|||||||
|
package ru.dbotthepony.kstarbound.defs.world.terrain
|
||||||
|
|
||||||
|
class TerrainSelectorFactory<D : Any, out T : AbstractTerrainSelector<D>>(val name: String, private val data: D, private val factory: (String, D, TerrainSelectorParameters) -> T) {
|
||||||
|
fun create(parameters: TerrainSelectorParameters): T {
|
||||||
|
return factory(name, data, parameters)
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,31 @@
|
|||||||
|
package ru.dbotthepony.kstarbound.defs.world.terrain
|
||||||
|
|
||||||
|
import ru.dbotthepony.kstarbound.json.builder.JsonFactory
|
||||||
|
import java.util.random.RandomGenerator
|
||||||
|
|
||||||
|
@JsonFactory
|
||||||
|
data class TerrainSelectorParameters(
|
||||||
|
val worldWidth: Int,
|
||||||
|
val baseHeight: Double,
|
||||||
|
val seed: Long = 0L,
|
||||||
|
val commonality: Double = 0.0
|
||||||
|
) {
|
||||||
|
fun withSeed(seed: Long) = copy(seed = seed)
|
||||||
|
fun withCommonality(commonality: Double) = copy(commonality = commonality)
|
||||||
|
private var random: RandomGenerator? = null
|
||||||
|
|
||||||
|
fun random(): RandomGenerator {
|
||||||
|
return random ?: ru.dbotthepony.kstarbound.util.random.random(seed)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun withRandom(): TerrainSelectorParameters {
|
||||||
|
if (random == null)
|
||||||
|
return withRandom(ru.dbotthepony.kstarbound.util.random.random(seed))
|
||||||
|
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
fun withRandom(randomGenerator: RandomGenerator): TerrainSelectorParameters {
|
||||||
|
return copy().also { it.random = randomGenerator }
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,75 @@
|
|||||||
|
package ru.dbotthepony.kstarbound.defs.world.terrain
|
||||||
|
|
||||||
|
import com.google.gson.Gson
|
||||||
|
import com.google.gson.JsonObject
|
||||||
|
import com.google.gson.JsonSyntaxException
|
||||||
|
import com.google.gson.TypeAdapter
|
||||||
|
import com.google.gson.TypeAdapterFactory
|
||||||
|
import com.google.gson.reflect.TypeToken
|
||||||
|
import com.google.gson.stream.JsonReader
|
||||||
|
import com.google.gson.stream.JsonWriter
|
||||||
|
import ru.dbotthepony.kommons.gson.consumeNull
|
||||||
|
import ru.dbotthepony.kommons.gson.value
|
||||||
|
import ru.dbotthepony.kstarbound.Registries
|
||||||
|
import ru.dbotthepony.kstarbound.Starbound
|
||||||
|
|
||||||
|
fun createNamedTerrainSelector(name: String, parameters: TerrainSelectorParameters): AbstractTerrainSelector<*> {
|
||||||
|
return Registries.terrainSelectors.getOrThrow(name).value.create(parameters)
|
||||||
|
}
|
||||||
|
|
||||||
|
enum class TerrainSelectorType(val jsonName: String) {
|
||||||
|
CONSTANT("constant"),
|
||||||
|
DISPLACEMENT("displacement"),
|
||||||
|
;
|
||||||
|
|
||||||
|
companion object : TypeAdapter<AbstractTerrainSelector<*>>(), TypeAdapterFactory {
|
||||||
|
override fun <T : Any> create(gson: Gson, type: TypeToken<T>): TypeAdapter<T>? {
|
||||||
|
if (AbstractTerrainSelector::class.java.isAssignableFrom(type.rawType)) {
|
||||||
|
return this as TypeAdapter<T>
|
||||||
|
}
|
||||||
|
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun write(out: JsonWriter, value: AbstractTerrainSelector<*>?) {
|
||||||
|
if (value == null)
|
||||||
|
out.nullValue()
|
||||||
|
else
|
||||||
|
out.value(value.toJson())
|
||||||
|
}
|
||||||
|
|
||||||
|
private val objects by lazy { Starbound.gson.getAdapter(JsonObject::class.java) }
|
||||||
|
|
||||||
|
override fun read(`in`: JsonReader): AbstractTerrainSelector<*>? {
|
||||||
|
if (`in`.consumeNull())
|
||||||
|
return null
|
||||||
|
|
||||||
|
return create(objects.read(`in`))
|
||||||
|
}
|
||||||
|
|
||||||
|
fun createFactory(json: JsonObject): TerrainSelectorFactory<*, *> {
|
||||||
|
val name = json["name"]?.asString ?: throw JsonSyntaxException("Missing 'name' element of terrain json")
|
||||||
|
val type = json["type"]?.asString?.lowercase() ?: throw JsonSyntaxException("Missing 'type' element of terrain json")
|
||||||
|
|
||||||
|
when (type) {
|
||||||
|
CONSTANT.jsonName -> {
|
||||||
|
return TerrainSelectorFactory(name, Starbound.gson.fromJson(json, ConstantTerrainSelector.Data::class.java), ::ConstantTerrainSelector)
|
||||||
|
}
|
||||||
|
|
||||||
|
DISPLACEMENT.jsonName -> {
|
||||||
|
return TerrainSelectorFactory(name, Starbound.gson.fromJson(json, DisplacementTerrainSelector.Data::class.java), ::DisplacementTerrainSelector)
|
||||||
|
}
|
||||||
|
|
||||||
|
else -> throw IllegalArgumentException("Unknown terrain selector type $type")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun create(json: JsonObject): AbstractTerrainSelector<*> {
|
||||||
|
return createFactory(json).create(Starbound.gson.fromJson(json["parameters"] ?: throw JsonSyntaxException("Missing 'parameters' element of terrain json"), TerrainSelectorParameters::class.java))
|
||||||
|
}
|
||||||
|
|
||||||
|
fun create(json: JsonObject, parameters: TerrainSelectorParameters): AbstractTerrainSelector<*> {
|
||||||
|
return createFactory(json).create(parameters)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -4,11 +4,14 @@ import it.unimi.dsi.fastutil.bytes.ByteArrayList
|
|||||||
import ru.dbotthepony.kommons.util.IStruct2d
|
import ru.dbotthepony.kommons.util.IStruct2d
|
||||||
import ru.dbotthepony.kommons.util.IStruct2i
|
import ru.dbotthepony.kommons.util.IStruct2i
|
||||||
import ru.dbotthepony.kommons.io.readDouble
|
import ru.dbotthepony.kommons.io.readDouble
|
||||||
|
import ru.dbotthepony.kommons.io.readFloat
|
||||||
import ru.dbotthepony.kommons.io.readLong
|
import ru.dbotthepony.kommons.io.readLong
|
||||||
import ru.dbotthepony.kommons.io.readSignedVarInt
|
import ru.dbotthepony.kommons.io.readSignedVarInt
|
||||||
import ru.dbotthepony.kommons.io.writeDouble
|
import ru.dbotthepony.kommons.io.writeDouble
|
||||||
|
import ru.dbotthepony.kommons.io.writeFloat
|
||||||
import ru.dbotthepony.kommons.io.writeLong
|
import ru.dbotthepony.kommons.io.writeLong
|
||||||
import ru.dbotthepony.kommons.io.writeSignedVarInt
|
import ru.dbotthepony.kommons.io.writeSignedVarInt
|
||||||
|
import ru.dbotthepony.kommons.math.RGBAColor
|
||||||
import ru.dbotthepony.kommons.vector.Vector2d
|
import ru.dbotthepony.kommons.vector.Vector2d
|
||||||
import ru.dbotthepony.kommons.vector.Vector2i
|
import ru.dbotthepony.kommons.vector.Vector2i
|
||||||
import ru.dbotthepony.kstarbound.world.ChunkPos
|
import ru.dbotthepony.kstarbound.world.ChunkPos
|
||||||
@ -40,3 +43,14 @@ fun InputStream.readHeader(header: String) {
|
|||||||
fun InputStream.readChunkPos(): ChunkPos {
|
fun InputStream.readChunkPos(): ChunkPos {
|
||||||
return ChunkPos(readSignedVarInt(), readSignedVarInt())
|
return ChunkPos(readSignedVarInt(), readSignedVarInt())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun OutputStream.writeColor(color: RGBAColor) {
|
||||||
|
writeFloat(color.red)
|
||||||
|
writeFloat(color.green)
|
||||||
|
writeFloat(color.blue)
|
||||||
|
writeFloat(color.alpha)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun InputStream.readColor(): RGBAColor {
|
||||||
|
return RGBAColor(readFloat(), readFloat(), readFloat(), readFloat())
|
||||||
|
}
|
||||||
|
@ -12,6 +12,7 @@ import com.google.gson.internal.bind.TypeAdapters
|
|||||||
import com.google.gson.stream.JsonReader
|
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
|
||||||
|
|
||||||
class InternedJsonElementAdapter(val stringInterner: Interner<String>) : TypeAdapter<JsonElement>() {
|
class InternedJsonElementAdapter(val stringInterner: Interner<String>) : TypeAdapter<JsonElement>() {
|
||||||
override fun write(out: JsonWriter, value: JsonElement?) {
|
override fun write(out: JsonWriter, value: JsonElement?) {
|
||||||
@ -36,7 +37,7 @@ class InternedJsonElementAdapter(val stringInterner: Interner<String>) : TypeAda
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun read(`in`: JsonReader): JsonObject? {
|
override fun read(`in`: JsonReader): JsonObject? {
|
||||||
if (`in`.peek() == JsonToken.NULL)
|
if (`in`.consumeNull())
|
||||||
return null
|
return null
|
||||||
|
|
||||||
val output = JsonObject()
|
val output = JsonObject()
|
||||||
@ -53,7 +54,7 @@ class InternedJsonElementAdapter(val stringInterner: Interner<String>) : TypeAda
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun read(`in`: JsonReader): JsonArray? {
|
override fun read(`in`: JsonReader): JsonArray? {
|
||||||
if (`in`.peek() == JsonToken.NULL)
|
if (`in`.consumeNull())
|
||||||
return null
|
return null
|
||||||
|
|
||||||
val output = JsonArray()
|
val output = JsonArray()
|
||||||
@ -78,7 +79,7 @@ class InternedStringAdapter(val stringInterner: Interner<String>) : TypeAdapter<
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun read(`in`: JsonReader): String? {
|
override fun read(`in`: JsonReader): String? {
|
||||||
if (`in`.peek() == JsonToken.NULL)
|
if (`in`.consumeNull())
|
||||||
return null
|
return null
|
||||||
|
|
||||||
if (`in`.peek() == JsonToken.BOOLEAN)
|
if (`in`.peek() == JsonToken.BOOLEAN)
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
package ru.dbotthepony.kstarbound.json
|
package ru.dbotthepony.kstarbound.json
|
||||||
|
|
||||||
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.Gson
|
import com.google.gson.Gson
|
||||||
import com.google.gson.JsonArray
|
import com.google.gson.JsonArray
|
||||||
@ -21,6 +22,10 @@ inline fun <reified C : Collection<E>, reified E> Gson.collectionAdapter(): Type
|
|||||||
return getAdapter(TypeToken.getParameterized(C::class.java, E::class.java)) as TypeAdapter<C>
|
return getAdapter(TypeToken.getParameterized(C::class.java, E::class.java)) as TypeAdapter<C>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
inline fun <reified K, reified V> Gson.mapAdapter(): TypeAdapter<ImmutableMap<K, V>> {
|
||||||
|
return getAdapter(TypeToken.getParameterized(ImmutableMap::class.java, K::class.java, V::class.java)) as TypeAdapter<ImmutableMap<K, V>>
|
||||||
|
}
|
||||||
|
|
||||||
inline fun <reified E> Gson.listAdapter(): TypeAdapter<ImmutableList<E>> {
|
inline fun <reified E> Gson.listAdapter(): TypeAdapter<ImmutableList<E>> {
|
||||||
return collectionAdapter()
|
return collectionAdapter()
|
||||||
}
|
}
|
||||||
|
@ -4,6 +4,7 @@ import com.google.gson.TypeAdapter
|
|||||||
import com.google.gson.stream.JsonReader
|
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
|
||||||
|
|
||||||
object LongRangeAdapter : TypeAdapter<LongRange>() {
|
object LongRangeAdapter : TypeAdapter<LongRange>() {
|
||||||
override fun write(out: JsonWriter, value: LongRange?) {
|
override fun write(out: JsonWriter, value: LongRange?) {
|
||||||
@ -18,7 +19,7 @@ object LongRangeAdapter : TypeAdapter<LongRange>() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun read(`in`: JsonReader): LongRange? {
|
override fun read(`in`: JsonReader): LongRange? {
|
||||||
if (`in`.peek() == JsonToken.NULL) {
|
if (`in`.consumeNull()) {
|
||||||
return null
|
return null
|
||||||
} else {
|
} else {
|
||||||
`in`.beginArray()
|
`in`.beginArray()
|
||||||
|
@ -65,7 +65,6 @@ annotation class JsonBuilder
|
|||||||
@Target(AnnotationTarget.CLASS)
|
@Target(AnnotationTarget.CLASS)
|
||||||
@Retention(AnnotationRetention.RUNTIME)
|
@Retention(AnnotationRetention.RUNTIME)
|
||||||
annotation class JsonFactory(
|
annotation class JsonFactory(
|
||||||
val storesJson: Boolean = false,
|
|
||||||
val asList: Boolean = false,
|
val asList: Boolean = false,
|
||||||
val logMisses: Boolean = false,
|
val logMisses: Boolean = false,
|
||||||
)
|
)
|
||||||
@ -89,6 +88,10 @@ annotation class JsonFactory(
|
|||||||
@Retention(AnnotationRetention.RUNTIME)
|
@Retention(AnnotationRetention.RUNTIME)
|
||||||
annotation class JsonImplementation(val implementingClass: KClass<*>)
|
annotation class JsonImplementation(val implementingClass: KClass<*>)
|
||||||
|
|
||||||
|
@Target(AnnotationTarget.CLASS)
|
||||||
|
@Retention(AnnotationRetention.RUNTIME)
|
||||||
|
annotation class JsonSingleton
|
||||||
|
|
||||||
object JsonImplementationTypeFactory : TypeAdapterFactory {
|
object JsonImplementationTypeFactory : TypeAdapterFactory {
|
||||||
override fun <T : Any?> create(gson: Gson, type: TypeToken<T>): TypeAdapter<T>? {
|
override fun <T : Any?> create(gson: Gson, type: TypeToken<T>): TypeAdapter<T>? {
|
||||||
val delegate = type.rawType.getAnnotation(JsonImplementation::class.java)
|
val delegate = type.rawType.getAnnotation(JsonImplementation::class.java)
|
||||||
|
@ -0,0 +1,84 @@
|
|||||||
|
package ru.dbotthepony.kstarbound.json.builder
|
||||||
|
|
||||||
|
import com.google.gson.Gson
|
||||||
|
import com.google.gson.JsonNull
|
||||||
|
import com.google.gson.JsonObject
|
||||||
|
import com.google.gson.JsonSyntaxException
|
||||||
|
import com.google.gson.TypeAdapter
|
||||||
|
import com.google.gson.TypeAdapterFactory
|
||||||
|
import com.google.gson.reflect.TypeToken
|
||||||
|
import com.google.gson.stream.JsonReader
|
||||||
|
import com.google.gson.stream.JsonWriter
|
||||||
|
import ru.dbotthepony.kommons.gson.consumeNull
|
||||||
|
import ru.dbotthepony.kommons.gson.value
|
||||||
|
|
||||||
|
inline fun <reified E : Any, reified C : Any> DispatchingAdapter(
|
||||||
|
key: String,
|
||||||
|
noinline value2type: C.() -> E,
|
||||||
|
noinline type2value: E.() -> TypeToken<out C>,
|
||||||
|
elements: Collection<E>,
|
||||||
|
): DispatchingAdapter<E, C> {
|
||||||
|
return DispatchingAdapter(key, value2type, type2value, elements, E::class.java, C::class.java)
|
||||||
|
}
|
||||||
|
|
||||||
|
class DispatchingAdapter<TYPE : Any, ELEMENT : Any>(
|
||||||
|
val key: String,
|
||||||
|
val value2type: ELEMENT.() -> TYPE,
|
||||||
|
val type2value: TYPE.() -> TypeToken<out ELEMENT>,
|
||||||
|
val types: Collection<TYPE>,
|
||||||
|
val typeClass: Class<TYPE>,
|
||||||
|
val baseValueClass: Class<ELEMENT>
|
||||||
|
) : TypeAdapterFactory {
|
||||||
|
override fun <T : Any> create(gson: Gson, type: TypeToken<T>): TypeAdapter<T>? {
|
||||||
|
if (type.rawType == baseValueClass) {
|
||||||
|
return Impl(gson) as TypeAdapter<T>
|
||||||
|
}
|
||||||
|
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
private inner class Impl(gson: Gson) : TypeAdapter<ELEMENT>() {
|
||||||
|
private val typeAdapter = gson.getAdapter(typeClass)
|
||||||
|
private val adapters = types.associateWith { gson.getAdapter(type2value.invoke(it)) } as Map<TYPE, TypeAdapter<ELEMENT>>
|
||||||
|
private val obj = gson.getAdapter(JsonObject::class.java)
|
||||||
|
|
||||||
|
override fun write(out: JsonWriter, value: ELEMENT?) {
|
||||||
|
if (value == null)
|
||||||
|
out.nullValue()
|
||||||
|
else {
|
||||||
|
val type = value2type(value)
|
||||||
|
val adapter = adapters[type] ?: throw JsonSyntaxException("Unknown type $type")
|
||||||
|
|
||||||
|
out.beginObject()
|
||||||
|
out.name(key)
|
||||||
|
typeAdapter.write(out, type)
|
||||||
|
|
||||||
|
when (val result = adapter.toJsonTree(value)) {
|
||||||
|
JsonNull.INSTANCE -> {} // do nothing, probably singleton value
|
||||||
|
|
||||||
|
// TODO: this is a considerable bottleneck, need proxied json writer which will hook right before top-most endObject()
|
||||||
|
is JsonObject -> {
|
||||||
|
for ((k, v) in result.entrySet()) {
|
||||||
|
out.name(k)
|
||||||
|
out.value(v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
else -> throw JsonSyntaxException("Expected JSON Object or JSON NULL, got ${result::class.java}")
|
||||||
|
}
|
||||||
|
|
||||||
|
out.endObject()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun read(`in`: JsonReader): ELEMENT? {
|
||||||
|
if (`in`.consumeNull())
|
||||||
|
return null
|
||||||
|
|
||||||
|
val read = obj.read(`in`)
|
||||||
|
val type = typeAdapter.fromJsonTree(read[key] ?: throw JsonSyntaxException("Missing '$key'"))
|
||||||
|
val adapter = adapters[type] ?: throw JsonSyntaxException("Unknown type $type (${read[key]})")
|
||||||
|
return adapter.fromJsonTree(read)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -14,8 +14,9 @@ import com.google.gson.stream.JsonWriter
|
|||||||
import it.unimi.dsi.fastutil.objects.Object2ObjectArrayMap
|
import it.unimi.dsi.fastutil.objects.Object2ObjectArrayMap
|
||||||
import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet
|
import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet
|
||||||
import org.apache.logging.log4j.LogManager
|
import org.apache.logging.log4j.LogManager
|
||||||
|
import ru.dbotthepony.kommons.gson.consumeNull
|
||||||
import ru.dbotthepony.kstarbound.util.sbIntern
|
import ru.dbotthepony.kstarbound.util.sbIntern
|
||||||
import java.util.Arrays
|
import java.util.*
|
||||||
import java.util.stream.Stream
|
import java.util.stream.Stream
|
||||||
import kotlin.reflect.KClass
|
import kotlin.reflect.KClass
|
||||||
import kotlin.reflect.full.isSuperclassOf
|
import kotlin.reflect.full.isSuperclassOf
|
||||||
@ -25,27 +26,22 @@ interface IStringSerializable {
|
|||||||
fun write(out: JsonWriter)
|
fun write(out: JsonWriter)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Suppress("FunctionName")
|
inline fun <reified T : Enum<T>> EnumAdapter(values: Stream<T> = Arrays.stream(T::class.java.enumConstants), default: T? = null): EnumAdapter<T> {
|
||||||
inline fun <reified T : Enum<T>>EnumAdapter(values: Stream<T> = Arrays.stream(T::class.java.enumConstants), default: T? = null): EnumAdapter<T> {
|
|
||||||
return EnumAdapter(T::class, values, default)
|
return EnumAdapter(T::class, values, default)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Suppress("FunctionName")
|
inline fun <reified T : Enum<T>> EnumAdapter(values: Iterator<T>, default: T? = null): EnumAdapter<T> {
|
||||||
inline fun <reified T : Enum<T>>EnumAdapter(values: Iterator<T>, default: T? = null): EnumAdapter<T> {
|
|
||||||
return EnumAdapter(T::class, Streams.stream(values), default)
|
return EnumAdapter(T::class, Streams.stream(values), default)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Suppress("FunctionName")
|
inline fun <reified T : Enum<T>> EnumAdapter(values: Array<out T>, default: T? = null): EnumAdapter<T> {
|
||||||
inline fun <reified T : Enum<T>>EnumAdapter(values: Array<out T>, default: T? = null): EnumAdapter<T> {
|
|
||||||
return EnumAdapter(T::class, Arrays.stream(values), default)
|
return EnumAdapter(T::class, Arrays.stream(values), default)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Suppress("FunctionName")
|
inline fun <reified T : Enum<T>> EnumAdapter(values: Collection<T>, default: T? = null): EnumAdapter<T> {
|
||||||
inline fun <reified T : Enum<T>>EnumAdapter(values: Collection<T>, default: T? = null): EnumAdapter<T> {
|
|
||||||
return EnumAdapter(T::class, values.stream(), default)
|
return EnumAdapter(T::class, values.stream(), default)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Suppress("name_shadowing")
|
|
||||||
class EnumAdapter<T : Enum<T>>(private val enum: KClass<T>, values: Stream<T> = Arrays.stream(enum.java.enumConstants), val default: T? = null) : TypeAdapter<T?>() {
|
class EnumAdapter<T : Enum<T>>(private val enum: KClass<T>, values: Stream<T> = Arrays.stream(enum.java.enumConstants), val default: T? = null) : TypeAdapter<T?>() {
|
||||||
constructor(clazz: Class<T>, values: Stream<T> = Arrays.stream(clazz.enumConstants), default: T? = null) : this(clazz.kotlin, values, default)
|
constructor(clazz: Class<T>, values: Stream<T> = Arrays.stream(clazz.enumConstants), default: T? = null) : this(clazz.kotlin, values, default)
|
||||||
|
|
||||||
@ -60,7 +56,7 @@ class EnumAdapter<T : Enum<T>>(private val enum: KClass<T>, values: Stream<T> =
|
|||||||
private val values = values.collect(ImmutableList.toImmutableList())
|
private val values = values.collect(ImmutableList.toImmutableList())
|
||||||
private val mapping: ImmutableMap<String, T>
|
private val mapping: ImmutableMap<String, T>
|
||||||
private val areCustom = IStringSerializable::class.java.isAssignableFrom(enum.java)
|
private val areCustom = IStringSerializable::class.java.isAssignableFrom(enum.java)
|
||||||
private val misses = ObjectOpenHashSet<String>()
|
private val misses = Collections.synchronizedSet(ObjectOpenHashSet<String>())
|
||||||
|
|
||||||
init {
|
init {
|
||||||
val builder = Object2ObjectArrayMap<String, T>()
|
val builder = Object2ObjectArrayMap<String, T>()
|
||||||
@ -88,6 +84,8 @@ class EnumAdapter<T : Enum<T>>(private val enum: KClass<T>, values: Stream<T> =
|
|||||||
override fun write(out: JsonWriter, value: T?) {
|
override fun write(out: JsonWriter, value: T?) {
|
||||||
if (value == null) {
|
if (value == null) {
|
||||||
out.nullValue()
|
out.nullValue()
|
||||||
|
} else if (value is IStringSerializable) {
|
||||||
|
value.write(out)
|
||||||
} else {
|
} else {
|
||||||
out.value(value.name)
|
out.value(value.name)
|
||||||
}
|
}
|
||||||
@ -95,7 +93,7 @@ class EnumAdapter<T : Enum<T>>(private val enum: KClass<T>, values: Stream<T> =
|
|||||||
|
|
||||||
@Suppress("unchecked_cast")
|
@Suppress("unchecked_cast")
|
||||||
override fun read(`in`: JsonReader): T? {
|
override fun read(`in`: JsonReader): T? {
|
||||||
if (`in`.peek() == JsonToken.NULL) {
|
if (`in`.consumeNull()) {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -17,6 +17,7 @@ import com.google.gson.reflect.TypeToken
|
|||||||
import com.google.gson.stream.JsonReader
|
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 it.unimi.dsi.fastutil.ints.Int2ObjectAVLTreeMap
|
||||||
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap
|
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap
|
||||||
import it.unimi.dsi.fastutil.ints.IntArrayList
|
import it.unimi.dsi.fastutil.ints.IntArrayList
|
||||||
import it.unimi.dsi.fastutil.objects.Object2ObjectArrayMap
|
import it.unimi.dsi.fastutil.objects.Object2ObjectArrayMap
|
||||||
@ -48,7 +49,6 @@ class FactoryAdapter<T : Any> private constructor(
|
|||||||
val types: ImmutableList<ReferencedProperty<T, *>>,
|
val types: ImmutableList<ReferencedProperty<T, *>>,
|
||||||
aliases: Map<String, List<String>>,
|
aliases: Map<String, List<String>>,
|
||||||
val asJsonArray: Boolean,
|
val asJsonArray: Boolean,
|
||||||
val storesJson: Boolean,
|
|
||||||
val stringInterner: Interner<String>,
|
val stringInterner: Interner<String>,
|
||||||
val logMisses: Boolean,
|
val logMisses: Boolean,
|
||||||
private val elements: TypeAdapter<JsonElement>
|
private val elements: TypeAdapter<JsonElement>
|
||||||
@ -78,10 +78,7 @@ class FactoryAdapter<T : Any> private constructor(
|
|||||||
* Обычный конструктор класса (без флагов "значения по умолчанию")
|
* Обычный конструктор класса (без флагов "значения по умолчанию")
|
||||||
*/
|
*/
|
||||||
private val regularFactory: KFunction<T> = clazz.constructors.firstOrNull first@{
|
private val regularFactory: KFunction<T> = clazz.constructors.firstOrNull first@{
|
||||||
var requiredSize = types.size
|
val requiredSize = types.size
|
||||||
|
|
||||||
if (storesJson)
|
|
||||||
requiredSize++
|
|
||||||
|
|
||||||
if (it.parameters.size == requiredSize) {
|
if (it.parameters.size == requiredSize) {
|
||||||
val iterator = types.iterator()
|
val iterator = types.iterator()
|
||||||
@ -99,20 +96,6 @@ class FactoryAdapter<T : Any> private constructor(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (storesJson) {
|
|
||||||
val nextParam = factoryIterator.next()
|
|
||||||
|
|
||||||
if (asJsonArray) {
|
|
||||||
if (!(nextParam.type.classifier as KClass<*>).isSubclassOf(List::class)) {
|
|
||||||
return@first false
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (!(nextParam.type.classifier as KClass<*>).isSubclassOf(Map::class)) {
|
|
||||||
return@first false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return@first true
|
return@first true
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -125,12 +108,6 @@ class FactoryAdapter<T : Any> private constructor(
|
|||||||
private val syntheticFactory: Constructor<T>? = try {
|
private val syntheticFactory: Constructor<T>? = try {
|
||||||
val typelist = types.map { (it.type.classifier as KClass<*>).java }.toMutableList()
|
val typelist = types.map { (it.type.classifier as KClass<*>).java }.toMutableList()
|
||||||
|
|
||||||
if (storesJson)
|
|
||||||
if (asJsonArray)
|
|
||||||
typelist.add(List::class.java)
|
|
||||||
else
|
|
||||||
typelist.add(Map::class.java)
|
|
||||||
|
|
||||||
for (i in 0 until (if (types.size % 31 == 0) types.size / 31 else types.size / 31 + 1))
|
for (i in 0 until (if (types.size % 31 == 0) types.size / 31 else types.size / 31 + 1))
|
||||||
typelist.add(Int::class.java)
|
typelist.add(Int::class.java)
|
||||||
|
|
||||||
@ -141,7 +118,7 @@ class FactoryAdapter<T : Any> private constructor(
|
|||||||
null
|
null
|
||||||
}
|
}
|
||||||
|
|
||||||
private val syntheticPrimitives = Int2ObjectOpenHashMap<Any>()
|
private val syntheticPrimitives = Int2ObjectAVLTreeMap<Any>()
|
||||||
|
|
||||||
init {
|
init {
|
||||||
if (syntheticFactory != null) {
|
if (syntheticFactory != null) {
|
||||||
@ -201,27 +178,14 @@ class FactoryAdapter<T : Any> private constructor(
|
|||||||
return null
|
return null
|
||||||
|
|
||||||
// таблица присутствия значений (если значение true то на i было значение внутри json)
|
// таблица присутствия значений (если значение true то на i было значение внутри json)
|
||||||
val presentValues = BooleanArray(types.size + (if (storesJson) 1 else 0))
|
val presentValues = BooleanArray(types.size)
|
||||||
var readValues = arrayOfNulls<Any>(types.size + (if (storesJson) 1 else 0))
|
var readValues = arrayOfNulls<Any>(types.size)
|
||||||
|
|
||||||
if (storesJson)
|
|
||||||
presentValues[presentValues.size - 1] = true
|
|
||||||
|
|
||||||
@Suppress("name_shadowing")
|
@Suppress("name_shadowing")
|
||||||
var reader = reader
|
var reader = reader
|
||||||
|
|
||||||
// Если нам необходимо читать объект как набор данных массива, то давай
|
// Если нам необходимо читать объект как набор данных массива, то давай
|
||||||
if (asJsonArray) {
|
if (asJsonArray) {
|
||||||
if (storesJson) {
|
|
||||||
val readArray = elements.read(reader)
|
|
||||||
|
|
||||||
if (readArray !is JsonArray)
|
|
||||||
throw JsonParseException("Expected JSON element to be an Array, ${readArray::class.qualifiedName} given")
|
|
||||||
|
|
||||||
reader = JsonTreeReader(readArray)
|
|
||||||
readValues[readValues.size - 1] = enrollList(flattenJsonElement(readArray, stringInterner) as List<Any>, stringInterner)
|
|
||||||
}
|
|
||||||
|
|
||||||
reader.beginArray()
|
reader.beginArray()
|
||||||
val iterator = types.iterator()
|
val iterator = types.iterator()
|
||||||
var fieldId = 0
|
var fieldId = 0
|
||||||
@ -230,7 +194,7 @@ class FactoryAdapter<T : Any> private constructor(
|
|||||||
if (!iterator.hasNext()) {
|
if (!iterator.hasNext()) {
|
||||||
val name = fieldId.toString()
|
val name = fieldId.toString()
|
||||||
|
|
||||||
if (!storesJson && logMisses && loggedMisses.add(name)) {
|
if (logMisses && loggedMisses.add(name)) {
|
||||||
LOGGER.warn("${clazz.qualifiedName} has no property for storing $name")
|
LOGGER.warn("${clazz.qualifiedName} has no property for storing $name")
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -255,7 +219,7 @@ class FactoryAdapter<T : Any> private constructor(
|
|||||||
} else {
|
} else {
|
||||||
var json: JsonObject by Delegates.notNull()
|
var json: JsonObject by Delegates.notNull()
|
||||||
|
|
||||||
if (storesJson || types.any { it.isFlat }) {
|
if (types.any { it.isFlat }) {
|
||||||
val readMap = elements.read(reader)
|
val readMap = elements.read(reader)
|
||||||
|
|
||||||
if (readMap !is JsonObject)
|
if (readMap !is JsonObject)
|
||||||
@ -263,9 +227,6 @@ class FactoryAdapter<T : Any> private constructor(
|
|||||||
|
|
||||||
json = readMap
|
json = readMap
|
||||||
reader = JsonTreeReader(readMap)
|
reader = JsonTreeReader(readMap)
|
||||||
|
|
||||||
if (storesJson)
|
|
||||||
readValues[readValues.size - 1] = enrollMap(flattenJsonElement(readMap, stringInterner) as Map<String, Any>, stringInterner)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
reader.beginObject()
|
reader.beginObject()
|
||||||
@ -275,7 +236,7 @@ class FactoryAdapter<T : Any> private constructor(
|
|||||||
val fields = name2index[name]
|
val fields = name2index[name]
|
||||||
|
|
||||||
if (fields == null || fields.size == 0) {
|
if (fields == null || fields.size == 0) {
|
||||||
if (!storesJson && logMisses && loggedMisses.add(name)) {
|
if (logMisses && loggedMisses.add(name)) {
|
||||||
LOGGER.warn("${clazz.qualifiedName} has no property for storing $name")
|
LOGGER.warn("${clazz.qualifiedName} has no property for storing $name")
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -393,7 +354,7 @@ class FactoryAdapter<T : Any> private constructor(
|
|||||||
if (readValues[i] != null) continue
|
if (readValues[i] != null) continue
|
||||||
val param = regularFactory.parameters[i]
|
val param = regularFactory.parameters[i]
|
||||||
|
|
||||||
if (param.isOptional && !presentValues[i]) {
|
if (param.isOptional && (!presentValues[i] || readValues[i] == null && i in syntheticPrimitives)) {
|
||||||
readValues[i] = syntheticPrimitives[i]
|
readValues[i] = syntheticPrimitives[i]
|
||||||
} else if (!param.isOptional) {
|
} else if (!param.isOptional) {
|
||||||
if (!presentValues[i]) throw JsonSyntaxException("Field ${field.name} of ${clazz.qualifiedName} is missing")
|
if (!presentValues[i]) throw JsonSyntaxException("Field ${field.name} of ${clazz.qualifiedName} is missing")
|
||||||
@ -401,7 +362,11 @@ class FactoryAdapter<T : Any> private constructor(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return syntheticFactory.newInstance(*readValues)
|
try {
|
||||||
|
return syntheticFactory.newInstance(*readValues)
|
||||||
|
} catch (err: Throwable) {
|
||||||
|
throw JsonSyntaxException("Failed to instance $clazz", err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -440,7 +405,6 @@ class FactoryAdapter<T : Any> private constructor(
|
|||||||
clazz = clazz,
|
clazz = clazz,
|
||||||
types = ImmutableList.copyOf(types.also { it.forEach{ it.resolve(gson) } }),
|
types = ImmutableList.copyOf(types.also { it.forEach{ it.resolve(gson) } }),
|
||||||
asJsonArray = asList,
|
asJsonArray = asList,
|
||||||
storesJson = storesJson,
|
|
||||||
stringInterner = stringInterner,
|
stringInterner = stringInterner,
|
||||||
aliases = aliases,
|
aliases = aliases,
|
||||||
logMisses = logMisses,
|
logMisses = logMisses,
|
||||||
@ -448,19 +412,6 @@ class FactoryAdapter<T : Any> private constructor(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Принимает ли класс *последним* аргументом JSON структуру
|
|
||||||
*
|
|
||||||
* На самом деле, JSON "заворачивается" в [ImmutableMap], или [ImmutableList] если указано [asList]/[inputAsList]
|
|
||||||
*
|
|
||||||
* Поэтому, конструктор класса ОБЯЗАН принимать [Map]/[ImmutableMap] или [List]/[ImmutableList] последним аргументом,
|
|
||||||
* иначе поиск конструктора завершится неудачей
|
|
||||||
*/
|
|
||||||
fun storesJson(flag: Boolean = true): Builder<T> {
|
|
||||||
storesJson = flag
|
|
||||||
return this
|
|
||||||
}
|
|
||||||
|
|
||||||
fun <V> add(field: KProperty1<T, V>, isFlat: Boolean = false, isMarkedNullable: Boolean? = null): Builder<T> {
|
fun <V> add(field: KProperty1<T, V>, isFlat: Boolean = false, isMarkedNullable: Boolean? = null): Builder<T> {
|
||||||
types.add(ReferencedProperty(field, isFlat = isFlat, isMarkedNullable = isMarkedNullable))
|
types.add(ReferencedProperty(field, isFlat = isFlat, isMarkedNullable = isMarkedNullable))
|
||||||
return this
|
return this
|
||||||
@ -517,7 +468,6 @@ class FactoryAdapter<T : Any> private constructor(
|
|||||||
builder.inputAsList()
|
builder.inputAsList()
|
||||||
}
|
}
|
||||||
|
|
||||||
builder.storesJson(config.storesJson)
|
|
||||||
builder.stringInterner = stringInterner
|
builder.stringInterner = stringInterner
|
||||||
builder.logMisses = config.logMisses
|
builder.logMisses = config.logMisses
|
||||||
|
|
||||||
@ -528,7 +478,7 @@ class FactoryAdapter<T : Any> private constructor(
|
|||||||
val foundConstructor = kclass.primaryConstructor ?: throw NoSuchElementException("Can't determine primary constructor for ${kclass.qualifiedName}")
|
val foundConstructor = kclass.primaryConstructor ?: throw NoSuchElementException("Can't determine primary constructor for ${kclass.qualifiedName}")
|
||||||
|
|
||||||
val params = foundConstructor.parameters
|
val params = foundConstructor.parameters
|
||||||
val lastIndex = if (config.storesJson) params.size - 1 else params.size
|
val lastIndex = params.size
|
||||||
|
|
||||||
for (i in 0 until lastIndex) {
|
for (i in 0 until lastIndex) {
|
||||||
val argument = params[i]
|
val argument = params[i]
|
||||||
|
@ -0,0 +1,15 @@
|
|||||||
|
package ru.dbotthepony.kstarbound.json.builder
|
||||||
|
|
||||||
|
import com.google.gson.TypeAdapter
|
||||||
|
import com.google.gson.stream.JsonReader
|
||||||
|
import com.google.gson.stream.JsonWriter
|
||||||
|
|
||||||
|
class SingletonTypeAdapter<V>(val instance: V) : TypeAdapter<V>() {
|
||||||
|
override fun write(out: JsonWriter, value: V) {
|
||||||
|
out.nullValue()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun read(`in`: JsonReader): V {
|
||||||
|
return instance
|
||||||
|
}
|
||||||
|
}
|
@ -1,17 +0,0 @@
|
|||||||
package ru.dbotthepony.kstarbound.json.factory
|
|
||||||
|
|
||||||
import com.google.gson.Gson
|
|
||||||
import com.google.gson.TypeAdapter
|
|
||||||
import com.google.gson.TypeAdapterFactory
|
|
||||||
import com.google.gson.reflect.TypeToken
|
|
||||||
import java.lang.reflect.ParameterizedType
|
|
||||||
|
|
||||||
object ArrayListAdapterFactory : TypeAdapterFactory {
|
|
||||||
override fun <T : Any?> create(gson: Gson, type: TypeToken<T>): TypeAdapter<T>? {
|
|
||||||
if (ArrayList::class.java.isAssignableFrom(type.rawType) && type.type is ParameterizedType) {
|
|
||||||
return ArrayListTypeAdapter(gson.getAdapter(TypeToken.get((type.type as ParameterizedType).actualTypeArguments[0]))) as TypeAdapter<T>
|
|
||||||
}
|
|
||||||
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,53 +0,0 @@
|
|||||||
package ru.dbotthepony.kstarbound.json.factory
|
|
||||||
|
|
||||||
import com.google.gson.TypeAdapter
|
|
||||||
import com.google.gson.stream.JsonReader
|
|
||||||
import com.google.gson.stream.JsonToken
|
|
||||||
import com.google.gson.stream.JsonWriter
|
|
||||||
import java.util.ArrayList
|
|
||||||
|
|
||||||
class ArrayListTypeAdapter<E>(val elementAdapter: TypeAdapter<E>) : TypeAdapter<ArrayList<E>>() {
|
|
||||||
override fun write(out: JsonWriter, value: ArrayList<E>?) {
|
|
||||||
if (value == null) {
|
|
||||||
out.nullValue()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if (value.size == 1) {
|
|
||||||
elementAdapter.write(out, value[0])
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
out.beginArray()
|
|
||||||
|
|
||||||
for (v in value) {
|
|
||||||
elementAdapter.write(out, v)
|
|
||||||
}
|
|
||||||
|
|
||||||
out.endArray()
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun read(reader: JsonReader): ArrayList<E>? {
|
|
||||||
if (reader.peek() == JsonToken.NULL)
|
|
||||||
return null
|
|
||||||
|
|
||||||
if (reader.peek() != JsonToken.BEGIN_ARRAY) {
|
|
||||||
// не массив, возможно упрощение структуры "a": [value] -> "a": value
|
|
||||||
val list = ArrayList<E>(1)
|
|
||||||
list.add(elementAdapter.read(reader))
|
|
||||||
return list
|
|
||||||
}
|
|
||||||
|
|
||||||
reader.beginArray()
|
|
||||||
|
|
||||||
val list = ArrayList<E>()
|
|
||||||
|
|
||||||
while (reader.peek() != JsonToken.END_ARRAY) {
|
|
||||||
list.add(elementAdapter.read(reader))
|
|
||||||
}
|
|
||||||
|
|
||||||
reader.endArray()
|
|
||||||
|
|
||||||
return list
|
|
||||||
}
|
|
||||||
}
|
|
@ -0,0 +1,71 @@
|
|||||||
|
package ru.dbotthepony.kstarbound.json.factory
|
||||||
|
|
||||||
|
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.JsonToken
|
||||||
|
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.longs.LongArrayList
|
||||||
|
import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet
|
||||||
|
import ru.dbotthepony.kommons.gson.consumeNull
|
||||||
|
import java.lang.reflect.ParameterizedType
|
||||||
|
|
||||||
|
object CollectionAdapterFactory : TypeAdapterFactory {
|
||||||
|
override fun <T : Any?> create(gson: Gson, type: TypeToken<T>): TypeAdapter<T>? {
|
||||||
|
if (type.type is ParameterizedType) {
|
||||||
|
val parentType = TypeToken.get((type.type as ParameterizedType).actualTypeArguments[0]) as TypeToken<Any>
|
||||||
|
|
||||||
|
return when (type.rawType) {
|
||||||
|
ArrayList::class.java -> Adapter<ArrayList<Any>, Any>(::ArrayList, gson.getAdapter(parentType))
|
||||||
|
List::class.java -> Adapter<ArrayList<Any>, Any>(::ArrayList, gson.getAdapter(parentType))
|
||||||
|
Set::class.java -> Adapter<ObjectOpenHashSet<Any>, Any>(::ObjectOpenHashSet, gson.getAdapter(parentType))
|
||||||
|
ObjectOpenHashSet::class.java -> Adapter<ObjectOpenHashSet<Any>, Any>(::ObjectOpenHashSet, gson.getAdapter(parentType))
|
||||||
|
else -> null
|
||||||
|
} as TypeAdapter<T>?
|
||||||
|
} else {
|
||||||
|
return when (type.rawType) {
|
||||||
|
IntArrayList::class.java -> Adapter(::IntArrayList, gson.getAdapter(Int::class.java))
|
||||||
|
LongArrayList::class.java -> Adapter(::LongArrayList, gson.getAdapter(Long::class.java))
|
||||||
|
DoubleArrayList::class.java -> Adapter(::DoubleArrayList, gson.getAdapter(Double::class.java))
|
||||||
|
else -> null
|
||||||
|
} as TypeAdapter<T>?
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class Adapter<C : MutableCollection<E>, E>(val factory: () -> C, val parent: TypeAdapter<E>) : TypeAdapter<C>() {
|
||||||
|
override fun write(out: JsonWriter, value: C?) {
|
||||||
|
if (value == null)
|
||||||
|
out.nullValue()
|
||||||
|
else {
|
||||||
|
out.beginArray()
|
||||||
|
for (v in value) parent.write(out, v)
|
||||||
|
out.endArray()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun read(`in`: JsonReader): C? {
|
||||||
|
if (`in`.consumeNull())
|
||||||
|
return null
|
||||||
|
|
||||||
|
val output = factory()
|
||||||
|
|
||||||
|
if (`in`.peek() == JsonToken.BEGIN_ARRAY) {
|
||||||
|
`in`.beginArray()
|
||||||
|
|
||||||
|
while (`in`.hasNext()) {
|
||||||
|
output.add(parent.read(`in`))
|
||||||
|
}
|
||||||
|
|
||||||
|
`in`.endArray()
|
||||||
|
} else {
|
||||||
|
output.add(parent.read(`in`))
|
||||||
|
}
|
||||||
|
|
||||||
|
return output
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -7,6 +7,7 @@ import com.google.gson.TypeAdapter
|
|||||||
import com.google.gson.stream.JsonReader
|
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
|
||||||
|
|
||||||
class ImmutableArrayMapTypeAdapter<K, V>(val keyAdapter: TypeAdapter<K>, val elementAdapter: TypeAdapter<V>) : TypeAdapter<ImmutableMap<K, V>>() {
|
class ImmutableArrayMapTypeAdapter<K, V>(val keyAdapter: TypeAdapter<K>, val elementAdapter: TypeAdapter<V>) : TypeAdapter<ImmutableMap<K, V>>() {
|
||||||
override fun write(out: JsonWriter, value: ImmutableMap<K, V>?) {
|
override fun write(out: JsonWriter, value: ImmutableMap<K, V>?) {
|
||||||
@ -26,7 +27,7 @@ class ImmutableArrayMapTypeAdapter<K, V>(val keyAdapter: TypeAdapter<K>, val ele
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun read(reader: JsonReader): ImmutableMap<K, V>? {
|
override fun read(reader: JsonReader): ImmutableMap<K, V>? {
|
||||||
if (reader.peek() == JsonToken.NULL)
|
if (reader.consumeNull())
|
||||||
return null
|
return null
|
||||||
|
|
||||||
reader.beginArray()
|
reader.beginArray()
|
||||||
|
@ -6,6 +6,7 @@ import com.google.gson.TypeAdapter
|
|||||||
import com.google.gson.stream.JsonReader
|
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
|
||||||
|
|
||||||
class ImmutableListTypeAdapter<E>(val elementAdapter: TypeAdapter<E>) : TypeAdapter<ImmutableList<E>>() {
|
class ImmutableListTypeAdapter<E>(val elementAdapter: TypeAdapter<E>) : TypeAdapter<ImmutableList<E>>() {
|
||||||
override fun write(out: JsonWriter, value: ImmutableList<E>?) {
|
override fun write(out: JsonWriter, value: ImmutableList<E>?) {
|
||||||
@ -14,11 +15,6 @@ class ImmutableListTypeAdapter<E>(val elementAdapter: TypeAdapter<E>) : TypeAdap
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if (value.size == 1) {
|
|
||||||
elementAdapter.write(out, value[0])
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
out.beginArray()
|
out.beginArray()
|
||||||
|
|
||||||
for (v in value) {
|
for (v in value) {
|
||||||
@ -29,14 +25,9 @@ class ImmutableListTypeAdapter<E>(val elementAdapter: TypeAdapter<E>) : TypeAdap
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun read(reader: JsonReader): ImmutableList<E>? {
|
override fun read(reader: JsonReader): ImmutableList<E>? {
|
||||||
if (reader.peek() == JsonToken.NULL)
|
if (reader.consumeNull())
|
||||||
return null
|
return null
|
||||||
|
|
||||||
if (reader.peek() != JsonToken.BEGIN_ARRAY) {
|
|
||||||
// не массив, возможно упрощение структуры "a": [value] -> "a": value
|
|
||||||
return ImmutableList.of(elementAdapter.read(reader) ?: throw JsonSyntaxException("List does not accept nulls"))
|
|
||||||
}
|
|
||||||
|
|
||||||
reader.beginArray()
|
reader.beginArray()
|
||||||
|
|
||||||
val builder = ImmutableList.Builder<E>()
|
val builder = ImmutableList.Builder<E>()
|
||||||
|
@ -8,6 +8,7 @@ import com.google.gson.TypeAdapter
|
|||||||
import com.google.gson.stream.JsonReader
|
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
|
||||||
|
|
||||||
class ImmutableMapTypeAdapter<V>(val stringInterner: Interner<String>, val elementAdapter: TypeAdapter<V>) : TypeAdapter<ImmutableMap<String, V>>() {
|
class ImmutableMapTypeAdapter<V>(val stringInterner: Interner<String>, val elementAdapter: TypeAdapter<V>) : TypeAdapter<ImmutableMap<String, V>>() {
|
||||||
override fun write(out: JsonWriter, value: ImmutableMap<String, V>?) {
|
override fun write(out: JsonWriter, value: ImmutableMap<String, V>?) {
|
||||||
@ -27,7 +28,7 @@ class ImmutableMapTypeAdapter<V>(val stringInterner: Interner<String>, val eleme
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun read(reader: JsonReader): ImmutableMap<String, V>? {
|
override fun read(reader: JsonReader): ImmutableMap<String, V>? {
|
||||||
if (reader.peek() == JsonToken.NULL)
|
if (reader.consumeNull())
|
||||||
return null
|
return null
|
||||||
|
|
||||||
if (reader.peek() == JsonToken.BEGIN_ARRAY) {
|
if (reader.peek() == JsonToken.BEGIN_ARRAY) {
|
||||||
|
@ -7,6 +7,7 @@ import com.google.gson.TypeAdapter
|
|||||||
import com.google.gson.stream.JsonReader
|
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
|
||||||
|
|
||||||
class ImmutableSetTypeAdapter<E>(val elementAdapter: TypeAdapter<E>) : TypeAdapter<ImmutableSet<E>>() {
|
class ImmutableSetTypeAdapter<E>(val elementAdapter: TypeAdapter<E>) : TypeAdapter<ImmutableSet<E>>() {
|
||||||
override fun write(out: JsonWriter, value: ImmutableSet<E>?) {
|
override fun write(out: JsonWriter, value: ImmutableSet<E>?) {
|
||||||
@ -15,11 +16,6 @@ class ImmutableSetTypeAdapter<E>(val elementAdapter: TypeAdapter<E>) : TypeAdapt
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if (value.size == 1) {
|
|
||||||
elementAdapter.write(out, value.first())
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
out.beginArray()
|
out.beginArray()
|
||||||
|
|
||||||
for (v in value) {
|
for (v in value) {
|
||||||
@ -30,14 +26,9 @@ class ImmutableSetTypeAdapter<E>(val elementAdapter: TypeAdapter<E>) : TypeAdapt
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun read(reader: JsonReader): ImmutableSet<E>? {
|
override fun read(reader: JsonReader): ImmutableSet<E>? {
|
||||||
if (reader.peek() == JsonToken.NULL)
|
if (reader.consumeNull())
|
||||||
return null
|
return null
|
||||||
|
|
||||||
if (reader.peek() != JsonToken.BEGIN_ARRAY) {
|
|
||||||
// не массив, возможно упрощение структуры "a": [value] -> "a": value
|
|
||||||
return ImmutableSet.of(elementAdapter.read(reader) ?: throw JsonSyntaxException("List does not accept nulls"))
|
|
||||||
}
|
|
||||||
|
|
||||||
reader.beginArray()
|
reader.beginArray()
|
||||||
|
|
||||||
val builder = ImmutableSet.Builder<E>()
|
val builder = ImmutableSet.Builder<E>()
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
package ru.dbotthepony.kstarbound.json
|
package ru.dbotthepony.kstarbound.json.factory
|
||||||
|
|
||||||
import com.github.benmanes.caffeine.cache.Interner
|
import com.github.benmanes.caffeine.cache.Interner
|
||||||
import com.google.gson.Gson
|
import com.google.gson.Gson
|
||||||
@ -11,7 +11,7 @@ import it.unimi.dsi.fastutil.objects.*
|
|||||||
import ru.dbotthepony.kommons.gson.consumeNull
|
import ru.dbotthepony.kommons.gson.consumeNull
|
||||||
import java.lang.reflect.ParameterizedType
|
import java.lang.reflect.ParameterizedType
|
||||||
|
|
||||||
class FastutilTypeAdapterFactory(private val interner: Interner<String>) : TypeAdapterFactory {
|
class MapsTypeAdapterFactory(private val interner: Interner<String>) : TypeAdapterFactory {
|
||||||
private fun map1(gson: Gson, type: TypeToken<*>, typeValue: TypeToken<*>, factoryHash: () -> Map<Any?, Any?>, factoryTree: () -> Map<Any?, Any?>): TypeAdapter<MutableMap<Any?, Any?>>? {
|
private fun map1(gson: Gson, type: TypeToken<*>, typeValue: TypeToken<*>, factoryHash: () -> Map<Any?, Any?>, factoryTree: () -> Map<Any?, Any?>): TypeAdapter<MutableMap<Any?, Any?>>? {
|
||||||
val p = type.type as? ParameterizedType ?: return null
|
val p = type.type as? ParameterizedType ?: return null
|
||||||
val typeKey = TypeToken.get(p.actualTypeArguments[0])
|
val typeKey = TypeToken.get(p.actualTypeArguments[0])
|
@ -0,0 +1,44 @@
|
|||||||
|
package ru.dbotthepony.kstarbound.json.factory
|
||||||
|
|
||||||
|
import com.google.gson.TypeAdapter
|
||||||
|
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.math.RGBAColor
|
||||||
|
|
||||||
|
// because despite description of "jsonToColor" in StarJsonExtra.hpp, they DO NOT support
|
||||||
|
// floats, color is always defined with 0-255 int components
|
||||||
|
object RGBAColorTypeAdapter : TypeAdapter<RGBAColor>() {
|
||||||
|
override fun write(out: JsonWriter, value: RGBAColor?) {
|
||||||
|
if (value == null)
|
||||||
|
out.nullValue()
|
||||||
|
else {
|
||||||
|
out.beginArray()
|
||||||
|
|
||||||
|
out.value(value.redInt)
|
||||||
|
out.value(value.greenInt)
|
||||||
|
out.value(value.blueInt)
|
||||||
|
if (value.alphaInt != 255) out.value(value.alphaInt)
|
||||||
|
|
||||||
|
out.endArray()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun read(`in`: JsonReader): RGBAColor? {
|
||||||
|
if (`in`.consumeNull())
|
||||||
|
return null
|
||||||
|
|
||||||
|
`in`.beginArray()
|
||||||
|
|
||||||
|
val red = `in`.nextInt()
|
||||||
|
val green = `in`.nextInt()
|
||||||
|
val blue = `in`.nextInt()
|
||||||
|
val alpha = `in`.peek().let { if (it == JsonToken.END_ARRAY) 255 else `in`.nextInt() }
|
||||||
|
|
||||||
|
val result = RGBAColor(red, green, blue, alpha)
|
||||||
|
|
||||||
|
`in`.endArray()
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,27 @@
|
|||||||
|
package ru.dbotthepony.kstarbound.json.factory
|
||||||
|
|
||||||
|
import com.google.gson.Gson
|
||||||
|
import com.google.gson.TypeAdapter
|
||||||
|
import com.google.gson.TypeAdapterFactory
|
||||||
|
import com.google.gson.reflect.TypeToken
|
||||||
|
import ru.dbotthepony.kstarbound.json.builder.JsonSingleton
|
||||||
|
import ru.dbotthepony.kstarbound.json.builder.SingletonTypeAdapter
|
||||||
|
|
||||||
|
object SingletonTypeAdapterFactory : TypeAdapterFactory {
|
||||||
|
override fun <T : Any> create(gson: Gson, type: TypeToken<T>): TypeAdapter<T>? {
|
||||||
|
val raw = type.rawType
|
||||||
|
|
||||||
|
if (raw.isAnnotationPresent(JsonSingleton::class.java)) {
|
||||||
|
val findInstance = raw.getDeclaredField("INSTANCE")
|
||||||
|
val f = findInstance.get(null) ?: throw NullPointerException("INSTANCE field is null")
|
||||||
|
|
||||||
|
if (!raw.isAssignableFrom(f::class.java)) {
|
||||||
|
throw ClassCastException("${f::class.java} can not be assigned to $raw")
|
||||||
|
}
|
||||||
|
|
||||||
|
return SingletonTypeAdapter(f) as TypeAdapter<T>
|
||||||
|
}
|
||||||
|
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
}
|
@ -247,7 +247,7 @@ private fun materialFootstepSound(context: ExecutionContext, arguments: Argument
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun materialHealth(context: ExecutionContext, arguments: ArgumentIterator) {
|
private fun materialHealth(context: ExecutionContext, arguments: ArgumentIterator) {
|
||||||
context.returnBuffer.setTo(lookupStrict(Registries.tiles, arguments.nextAny()).value.damageConfig.health)
|
context.returnBuffer.setTo(lookupStrict(Registries.tiles, arguments.nextAny()).value.damageConfig.totalHealth)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun liquidName(context: ExecutionContext, arguments: ArgumentIterator) {
|
private fun liquidName(context: ExecutionContext, arguments: ArgumentIterator) {
|
||||||
|
@ -7,19 +7,25 @@ import io.netty.channel.ChannelInboundHandlerAdapter
|
|||||||
import io.netty.channel.ChannelOption
|
import io.netty.channel.ChannelOption
|
||||||
import io.netty.channel.nio.NioEventLoopGroup
|
import io.netty.channel.nio.NioEventLoopGroup
|
||||||
import org.apache.logging.log4j.LogManager
|
import org.apache.logging.log4j.LogManager
|
||||||
|
import ru.dbotthepony.kommons.util.KOptional
|
||||||
|
import ru.dbotthepony.kstarbound.network.packets.ClientContextUpdatePacket
|
||||||
import ru.dbotthepony.kstarbound.player.Avatar
|
import ru.dbotthepony.kstarbound.player.Avatar
|
||||||
import ru.dbotthepony.kstarbound.world.entities.PlayerEntity
|
import ru.dbotthepony.kstarbound.world.entities.PlayerEntity
|
||||||
import java.io.Closeable
|
import java.io.Closeable
|
||||||
import java.util.*
|
import java.util.*
|
||||||
import kotlin.properties.Delegates
|
import kotlin.properties.Delegates
|
||||||
|
|
||||||
abstract class Connection(val side: ConnectionSide, val type: ConnectionType, val localUUID: UUID) : ChannelInboundHandlerAdapter(), Closeable {
|
abstract class Connection(val side: ConnectionSide, val type: ConnectionType) : ChannelInboundHandlerAdapter(), Closeable {
|
||||||
abstract override fun channelRead(ctx: ChannelHandlerContext, msg: Any)
|
abstract override fun channelRead(ctx: ChannelHandlerContext, msg: Any)
|
||||||
|
|
||||||
var avatar: Avatar? = null
|
var avatar: Avatar? = null
|
||||||
var character: PlayerEntity? = null
|
var character: PlayerEntity? = null
|
||||||
|
val rpc = JsonRPC()
|
||||||
|
|
||||||
var channel: Channel by Delegates.notNull()
|
var connectionID: Int = -1
|
||||||
|
|
||||||
|
val hasChannel get() = ::channel.isInitialized
|
||||||
|
lateinit var channel: Channel
|
||||||
protected set
|
protected set
|
||||||
|
|
||||||
var isLegacy: Boolean = true
|
var isLegacy: Boolean = true
|
||||||
@ -37,7 +43,8 @@ abstract class Connection(val side: ConnectionSide, val type: ConnectionType, va
|
|||||||
private val legacyValidator = PacketRegistry.LEGACY.Validator(side)
|
private val legacyValidator = PacketRegistry.LEGACY.Validator(side)
|
||||||
private val legacySerializer = PacketRegistry.LEGACY.Serializer(side)
|
private val legacySerializer = PacketRegistry.LEGACY.Serializer(side)
|
||||||
|
|
||||||
fun setupLegacy() {
|
open fun setupLegacy() {
|
||||||
|
if (isConnected) throw IllegalStateException("Already connected")
|
||||||
LOGGER.info("Handshake successful on ${channel.remoteAddress()}, channel is using legacy protocol")
|
LOGGER.info("Handshake successful on ${channel.remoteAddress()}, channel is using legacy protocol")
|
||||||
|
|
||||||
if (type == ConnectionType.MEMORY) {
|
if (type == ConnectionType.MEMORY) {
|
||||||
@ -52,7 +59,8 @@ abstract class Connection(val side: ConnectionSide, val type: ConnectionType, va
|
|||||||
isConnected = true
|
isConnected = true
|
||||||
}
|
}
|
||||||
|
|
||||||
fun setupNative() {
|
open fun setupNative() {
|
||||||
|
if (isConnected) throw IllegalStateException("Already connected")
|
||||||
LOGGER.info("Handshake successful on ${channel.remoteAddress()}, channel is using native protocol")
|
LOGGER.info("Handshake successful on ${channel.remoteAddress()}, channel is using native protocol")
|
||||||
|
|
||||||
if (type == ConnectionType.MEMORY) {
|
if (type == ConnectionType.MEMORY) {
|
||||||
@ -65,8 +73,11 @@ abstract class Connection(val side: ConnectionSide, val type: ConnectionType, va
|
|||||||
|
|
||||||
isLegacy = false
|
isLegacy = false
|
||||||
isConnected = true
|
isConnected = true
|
||||||
|
}
|
||||||
|
|
||||||
inGame()
|
protected open fun onChannelClosed() {
|
||||||
|
isConnected = false
|
||||||
|
LOGGER.info("Connection to ${channel.remoteAddress()} is closed")
|
||||||
}
|
}
|
||||||
|
|
||||||
fun bind(channel: Channel) {
|
fun bind(channel: Channel) {
|
||||||
@ -81,12 +92,11 @@ abstract class Connection(val side: ConnectionSide, val type: ConnectionType, va
|
|||||||
channel.pipeline().addLast(this)
|
channel.pipeline().addLast(this)
|
||||||
|
|
||||||
channel.closeFuture().addListener {
|
channel.closeFuture().addListener {
|
||||||
isConnected = false
|
onChannelClosed()
|
||||||
LOGGER.info("Connection to ${channel.remoteAddress()} is closed")
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected abstract fun inGame()
|
abstract fun inGame()
|
||||||
|
|
||||||
fun send(packet: IPacket) {
|
fun send(packet: IPacket) {
|
||||||
channel.write(packet)
|
channel.write(packet)
|
||||||
@ -97,7 +107,7 @@ abstract class Connection(val side: ConnectionSide, val type: ConnectionType, va
|
|||||||
channel.flush()
|
channel.flush()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun flush() {
|
open fun flush() {
|
||||||
channel.flush()
|
channel.flush()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
137
src/main/kotlin/ru/dbotthepony/kstarbound/network/JsonRPC.kt
Normal file
137
src/main/kotlin/ru/dbotthepony/kstarbound/network/JsonRPC.kt
Normal file
@ -0,0 +1,137 @@
|
|||||||
|
package ru.dbotthepony.kstarbound.network
|
||||||
|
|
||||||
|
import com.google.common.collect.ImmutableList
|
||||||
|
import com.google.gson.JsonElement
|
||||||
|
import com.google.gson.JsonNull
|
||||||
|
import com.google.gson.JsonObject
|
||||||
|
import com.google.gson.JsonSyntaxException
|
||||||
|
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap
|
||||||
|
import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap
|
||||||
|
import org.apache.logging.log4j.LogManager
|
||||||
|
import ru.dbotthepony.kommons.gson.set
|
||||||
|
import ru.dbotthepony.kommons.io.readBinaryString
|
||||||
|
import ru.dbotthepony.kommons.io.readKOptional
|
||||||
|
import ru.dbotthepony.kommons.io.writeBinaryString
|
||||||
|
import ru.dbotthepony.kommons.io.writeKOptional
|
||||||
|
import ru.dbotthepony.kommons.util.KOptional
|
||||||
|
import ru.dbotthepony.kstarbound.json.readJsonElement
|
||||||
|
import ru.dbotthepony.kstarbound.json.writeJsonElement
|
||||||
|
import java.io.DataInputStream
|
||||||
|
import java.io.DataOutputStream
|
||||||
|
import java.util.concurrent.CompletableFuture
|
||||||
|
import java.util.concurrent.locks.ReentrantLock
|
||||||
|
import kotlin.concurrent.withLock
|
||||||
|
|
||||||
|
class JsonRPC {
|
||||||
|
enum class Command {
|
||||||
|
REQUEST, RESPONSE, FAIL;
|
||||||
|
}
|
||||||
|
|
||||||
|
data class Entry(val command: Command, val id: Int, val handler: KOptional<String>, val arguments: KOptional<JsonElement>) {
|
||||||
|
fun write(stream: DataOutputStream, isLegacy: Boolean) {
|
||||||
|
if (isLegacy) {
|
||||||
|
stream.writeJsonElement(JsonObject().also {
|
||||||
|
it["command"] = command.name.lowercase()
|
||||||
|
it["id"] = id
|
||||||
|
handler.ifPresent { v -> it["handler"] = v }
|
||||||
|
arguments.ifPresent { v -> if (command == Command.RESPONSE) it["result"] = v else it["arguments"] = v }
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
stream.write(command.ordinal)
|
||||||
|
stream.writeInt(id)
|
||||||
|
stream.writeKOptional(handler) { writeBinaryString(it) }
|
||||||
|
stream.writeKOptional(arguments) { writeJsonElement(it) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
fun native(stream: DataInputStream): Entry {
|
||||||
|
return Entry(Command.entries[stream.read()], stream.readInt(), stream.readKOptional { readBinaryString() }, stream.readKOptional { readJsonElement() })
|
||||||
|
}
|
||||||
|
|
||||||
|
fun legacy(stream: DataInputStream): Entry {
|
||||||
|
val data = stream.readJsonElement()
|
||||||
|
check(data is JsonObject) { "Expected JsonObject, got ${data::class}" }
|
||||||
|
val command = data["command"]?.asString?.uppercase() ?: throw JsonSyntaxException("Missing 'command' in RPC data")
|
||||||
|
val id = data["id"]?.asInt ?: throw JsonSyntaxException("Missing 'id' in RPC data")
|
||||||
|
val handler = KOptional.ofNullable(data["handler"]?.asString)
|
||||||
|
val arguments = KOptional.ofNullable(data["arguments"])
|
||||||
|
return Entry(Command.entries.firstOrNull { it.name == command } ?: throw JsonSyntaxException("Invalid 'command': $command"), id, handler, arguments)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun read(stream: DataInputStream, isLegacy: Boolean): Entry {
|
||||||
|
if (isLegacy)
|
||||||
|
return legacy(stream)
|
||||||
|
else
|
||||||
|
return native(stream)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun interface Callback {
|
||||||
|
operator fun invoke(arguments: JsonElement): JsonElement
|
||||||
|
}
|
||||||
|
|
||||||
|
private var commandCounter = 0
|
||||||
|
private val pendingWrite = ArrayList<Entry>()
|
||||||
|
private val responses = Int2ObjectOpenHashMap<CompletableFuture<JsonElement>>()
|
||||||
|
private val lock = ReentrantLock()
|
||||||
|
private val handlers = Object2ObjectOpenHashMap<String, Callback>()
|
||||||
|
|
||||||
|
fun add(name: String, callback: Callback): (JsonElement) -> CompletableFuture<JsonElement> {
|
||||||
|
val old = handlers.put(name, callback)
|
||||||
|
check(old == null) { "Duplicate RPC handler: $name" }
|
||||||
|
return { invoke(name, it) }
|
||||||
|
}
|
||||||
|
|
||||||
|
operator fun invoke(handler: String, arguments: JsonElement): CompletableFuture<JsonElement> {
|
||||||
|
lock.withLock {
|
||||||
|
val id = commandCounter++
|
||||||
|
val response = CompletableFuture<JsonElement>()
|
||||||
|
responses[id] = response
|
||||||
|
pendingWrite.add(Entry(Command.REQUEST, id, KOptional(handler), KOptional(arguments.deepCopy())))
|
||||||
|
return response
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun write(): List<Entry>? {
|
||||||
|
lock.withLock {
|
||||||
|
if (pendingWrite.isEmpty())
|
||||||
|
return null
|
||||||
|
|
||||||
|
val result = ImmutableList.copyOf(pendingWrite)
|
||||||
|
pendingWrite.clear()
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun read(data: List<Entry>) {
|
||||||
|
lock.withLock {
|
||||||
|
for (entry in data) {
|
||||||
|
try {
|
||||||
|
when (entry.command) {
|
||||||
|
Command.REQUEST -> {
|
||||||
|
val handler = handlers[entry.handler.value] ?: throw IllegalArgumentException("No such handler ${entry.handler.value}")
|
||||||
|
pendingWrite.add(Entry(Command.RESPONSE, entry.id, KOptional(), KOptional(handler(entry.arguments.value))))
|
||||||
|
}
|
||||||
|
|
||||||
|
Command.RESPONSE -> {
|
||||||
|
responses.remove(entry.id)?.complete(entry.arguments.orElse { JsonNull.INSTANCE })
|
||||||
|
}
|
||||||
|
|
||||||
|
Command.FAIL -> {
|
||||||
|
responses.remove(entry.id)?.completeExceptionally(RuntimeException("Remote RPC call failed"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (err: Throwable) {
|
||||||
|
LOGGER.error("Error while handling RPC call", err)
|
||||||
|
pendingWrite.add(Entry(Command.FAIL, entry.id, KOptional(), KOptional()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private val LOGGER = LogManager.getLogger()
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,129 @@
|
|||||||
|
package ru.dbotthepony.kstarbound.network
|
||||||
|
|
||||||
|
import ru.dbotthepony.kommons.io.readVarInt
|
||||||
|
import ru.dbotthepony.kommons.io.writeVarInt
|
||||||
|
import ru.dbotthepony.kstarbound.defs.tile.BuiltinMetaMaterials
|
||||||
|
import java.io.DataInputStream
|
||||||
|
import java.io.DataOutputStream
|
||||||
|
|
||||||
|
data class LegacyNetworkTileState(
|
||||||
|
val tile: Int, // ushort
|
||||||
|
val tileHueShift: Int, // ubyte
|
||||||
|
val tileColor: Int, // ubyte
|
||||||
|
val tileModifier: Int, // ushort
|
||||||
|
val tileModifierHueShift: Int, // ubyte
|
||||||
|
) {
|
||||||
|
fun write(stream: DataOutputStream) {
|
||||||
|
if (tile !in 1 .. 65535) { // empty or can't be represented by legacy protocol
|
||||||
|
// very interesting, very yes
|
||||||
|
// what about literal 0 material?
|
||||||
|
stream.writeShort(0)
|
||||||
|
} else {
|
||||||
|
stream.writeShort(tile)
|
||||||
|
stream.write(tileHueShift)
|
||||||
|
stream.write(tileColor)
|
||||||
|
|
||||||
|
if (tileModifier !in 1 .. 65535) { // empty or can't be represented by legacy protocol
|
||||||
|
stream.writeShort(0)
|
||||||
|
} else {
|
||||||
|
stream.writeShort(tileModifier)
|
||||||
|
stream.write(tileModifierHueShift)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
val EMPTY = LegacyNetworkTileState(0, 0, 0, 0, 0)
|
||||||
|
val NULL = LegacyNetworkTileState(BuiltinMetaMaterials.NULL.id!!, 0, 0, 0, 0)
|
||||||
|
|
||||||
|
fun read(stream: DataInputStream): LegacyNetworkTileState {
|
||||||
|
val tile = stream.readUnsignedShort()
|
||||||
|
val tileHueShift: Int
|
||||||
|
val tileColor: Int
|
||||||
|
val tileModifier: Int
|
||||||
|
val tileModifierHueShift: Int
|
||||||
|
|
||||||
|
if (tile == 0) {
|
||||||
|
return EMPTY
|
||||||
|
} else {
|
||||||
|
tileHueShift = stream.readUnsignedByte()
|
||||||
|
tileColor = stream.readUnsignedByte()
|
||||||
|
tileModifier = stream.readUnsignedShort()
|
||||||
|
|
||||||
|
if (tileModifier == 0) {
|
||||||
|
tileModifierHueShift = 0
|
||||||
|
} else {
|
||||||
|
tileModifierHueShift = stream.readUnsignedByte()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return LegacyNetworkTileState(tile, tileHueShift, tileColor, tileModifier, tileModifierHueShift)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
data class LegacyNetworkCellState(
|
||||||
|
val background: LegacyNetworkTileState,
|
||||||
|
val foreground: LegacyNetworkTileState,
|
||||||
|
|
||||||
|
val collisionType: Int, // ubyte
|
||||||
|
val blockBiomeIndex: Int, // ubyte
|
||||||
|
val environmentBiomeIndex: Int, // ubyte
|
||||||
|
val liquid: LegacyNetworkLiquidState,
|
||||||
|
val dungeonId: Int, // ushort
|
||||||
|
) {
|
||||||
|
fun write(stream: DataOutputStream) {
|
||||||
|
background.write(stream)
|
||||||
|
foreground.write(stream)
|
||||||
|
|
||||||
|
stream.write(collisionType)
|
||||||
|
stream.write(blockBiomeIndex)
|
||||||
|
stream.write(environmentBiomeIndex)
|
||||||
|
liquid.write(stream)
|
||||||
|
stream.writeVarInt(dungeonId)
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
val EMPTY = LegacyNetworkCellState(LegacyNetworkTileState.EMPTY, LegacyNetworkTileState.EMPTY, 0, 0, 0, LegacyNetworkLiquidState.EMPTY, 0)
|
||||||
|
val NULL = LegacyNetworkCellState(LegacyNetworkTileState.NULL, LegacyNetworkTileState.NULL, 0, 0, 0, LegacyNetworkLiquidState.EMPTY, 0)
|
||||||
|
|
||||||
|
fun read(stream: DataInputStream): LegacyNetworkCellState {
|
||||||
|
return LegacyNetworkCellState(
|
||||||
|
LegacyNetworkTileState.read(stream),
|
||||||
|
LegacyNetworkTileState.read(stream),
|
||||||
|
stream.readUnsignedByte(),
|
||||||
|
stream.readUnsignedByte(),
|
||||||
|
stream.readUnsignedByte(),
|
||||||
|
LegacyNetworkLiquidState.read(stream),
|
||||||
|
stream.readVarInt()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
data class LegacyNetworkLiquidState(
|
||||||
|
val liquid: Int, // ubyte
|
||||||
|
val level: Int, // ubyte
|
||||||
|
) {
|
||||||
|
fun write(stream: DataOutputStream) {
|
||||||
|
stream.write(liquid)
|
||||||
|
|
||||||
|
if (liquid in 1 .. 255) { // empty or can't be represented by legacy protocol
|
||||||
|
stream.write(level)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
val EMPTY = LegacyNetworkLiquidState(0, 0)
|
||||||
|
|
||||||
|
fun read(stream: DataInputStream): LegacyNetworkLiquidState {
|
||||||
|
val liquid = stream.readUnsignedByte()
|
||||||
|
|
||||||
|
if (liquid in 1 .. 255) {
|
||||||
|
return LegacyNetworkLiquidState(liquid, stream.readUnsignedByte())
|
||||||
|
}
|
||||||
|
|
||||||
|
return EMPTY
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -13,28 +13,34 @@ import it.unimi.dsi.fastutil.io.FastByteArrayOutputStream
|
|||||||
import it.unimi.dsi.fastutil.objects.Reference2ObjectOpenHashMap
|
import it.unimi.dsi.fastutil.objects.Reference2ObjectOpenHashMap
|
||||||
import org.apache.logging.log4j.LogManager
|
import org.apache.logging.log4j.LogManager
|
||||||
import ru.dbotthepony.kommons.io.readSignedVarInt
|
import ru.dbotthepony.kommons.io.readSignedVarInt
|
||||||
import ru.dbotthepony.kommons.io.readVarInt
|
|
||||||
import ru.dbotthepony.kommons.io.writeSignedVarInt
|
import ru.dbotthepony.kommons.io.writeSignedVarInt
|
||||||
import ru.dbotthepony.kommons.io.writeVarInt
|
|
||||||
import ru.dbotthepony.kstarbound.client.network.packets.ForgetChunkPacket
|
import ru.dbotthepony.kstarbound.client.network.packets.ForgetChunkPacket
|
||||||
import ru.dbotthepony.kstarbound.client.network.packets.ChunkCellsPacket
|
import ru.dbotthepony.kstarbound.client.network.packets.ChunkCellsPacket
|
||||||
import ru.dbotthepony.kstarbound.client.network.packets.ForgetEntityPacket
|
import ru.dbotthepony.kstarbound.client.network.packets.ForgetEntityPacket
|
||||||
import ru.dbotthepony.kstarbound.client.network.packets.JoinWorldPacket
|
import ru.dbotthepony.kstarbound.client.network.packets.JoinWorldPacket
|
||||||
import ru.dbotthepony.kstarbound.client.network.packets.SpawnWorldObjectPacket
|
import ru.dbotthepony.kstarbound.client.network.packets.SpawnWorldObjectPacket
|
||||||
|
import ru.dbotthepony.kstarbound.network.packets.ClientContextUpdatePacket
|
||||||
|
import ru.dbotthepony.kstarbound.network.packets.PingPacket
|
||||||
|
import ru.dbotthepony.kstarbound.network.packets.PongPacket
|
||||||
import ru.dbotthepony.kstarbound.network.packets.serverbound.ClientConnectPacket
|
import ru.dbotthepony.kstarbound.network.packets.serverbound.ClientConnectPacket
|
||||||
import ru.dbotthepony.kstarbound.network.packets.clientbound.ConnectSuccessPacket
|
import ru.dbotthepony.kstarbound.network.packets.clientbound.ConnectSuccessPacket
|
||||||
import ru.dbotthepony.kstarbound.network.packets.clientbound.HandshakeChallengePacket
|
import ru.dbotthepony.kstarbound.network.packets.clientbound.HandshakeChallengePacket
|
||||||
import ru.dbotthepony.kstarbound.network.packets.serverbound.HandshakeResponsePacket
|
import ru.dbotthepony.kstarbound.network.packets.serverbound.HandshakeResponsePacket
|
||||||
import ru.dbotthepony.kstarbound.network.packets.ProtocolRequestPacket
|
import ru.dbotthepony.kstarbound.network.packets.ProtocolRequestPacket
|
||||||
import ru.dbotthepony.kstarbound.network.packets.ProtocolResponsePacket
|
import ru.dbotthepony.kstarbound.network.packets.ProtocolResponsePacket
|
||||||
|
import ru.dbotthepony.kstarbound.network.packets.clientbound.LegacyTileArrayUpdatePacket
|
||||||
|
import ru.dbotthepony.kstarbound.network.packets.clientbound.LegacyTileUpdatePacket
|
||||||
import ru.dbotthepony.kstarbound.network.packets.clientbound.ServerDisconnectPacket
|
import ru.dbotthepony.kstarbound.network.packets.clientbound.ServerDisconnectPacket
|
||||||
import ru.dbotthepony.kstarbound.network.packets.clientbound.WorldStartPacket
|
import ru.dbotthepony.kstarbound.network.packets.clientbound.WorldStartPacket
|
||||||
|
import ru.dbotthepony.kstarbound.network.packets.clientbound.WorldStopPacket
|
||||||
import ru.dbotthepony.kstarbound.server.network.packets.TrackedPositionPacket
|
import ru.dbotthepony.kstarbound.server.network.packets.TrackedPositionPacket
|
||||||
import ru.dbotthepony.kstarbound.server.network.packets.TrackedSizePacket
|
import ru.dbotthepony.kstarbound.server.network.packets.TrackedSizePacket
|
||||||
import java.io.BufferedInputStream
|
import java.io.BufferedInputStream
|
||||||
import java.io.DataInputStream
|
import java.io.DataInputStream
|
||||||
import java.io.DataOutputStream
|
import java.io.DataOutputStream
|
||||||
|
import java.io.FilterInputStream
|
||||||
import java.io.InputStream
|
import java.io.InputStream
|
||||||
|
import java.util.zip.Deflater
|
||||||
import java.util.zip.InflaterInputStream
|
import java.util.zip.InflaterInputStream
|
||||||
import kotlin.math.absoluteValue
|
import kotlin.math.absoluteValue
|
||||||
import kotlin.reflect.KClass
|
import kotlin.reflect.KClass
|
||||||
@ -66,6 +72,10 @@ class PacketRegistry(val isLegacy: Boolean) {
|
|||||||
return add(T::class, reader, direction)
|
return add(T::class, reader, direction)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private inline fun <reified T : IPacket> add(value: T, direction: PacketDirection = PacketDirection.get(T::class)): PacketRegistry {
|
||||||
|
return add(T::class, { _, _ -> value }, direction)
|
||||||
|
}
|
||||||
|
|
||||||
private fun skip(amount: Int = 1) {
|
private fun skip(amount: Int = 1) {
|
||||||
for (i in 0 until amount) {
|
for (i in 0 until amount) {
|
||||||
packets.add(null)
|
packets.add(null)
|
||||||
@ -77,6 +87,38 @@ class PacketRegistry(val isLegacy: Boolean) {
|
|||||||
packets.add(null)
|
packets.add(null)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// avoid zip bomb
|
||||||
|
private class LimitingInputStream(inputStream: InputStream) : FilterInputStream(inputStream) {
|
||||||
|
private var read = 0L
|
||||||
|
|
||||||
|
override fun read(): Int {
|
||||||
|
if (read >= MAX_PACKET_SIZE)
|
||||||
|
return -1
|
||||||
|
|
||||||
|
read++
|
||||||
|
return super.read()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun read(b: ByteArray, off: Int, len: Int): Int {
|
||||||
|
if (read >= MAX_PACKET_SIZE)
|
||||||
|
return -1
|
||||||
|
|
||||||
|
val actual = super.read(b, off, len.coerceAtMost((MAX_PACKET_SIZE - read).coerceAtMost(Int.MAX_VALUE.toLong()).toInt()))
|
||||||
|
|
||||||
|
if (actual > 0)
|
||||||
|
read += actual
|
||||||
|
|
||||||
|
return actual
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun available(): Int {
|
||||||
|
if (read >= MAX_PACKET_SIZE)
|
||||||
|
return 0
|
||||||
|
else
|
||||||
|
return super.available()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
inner class Serializer(val side: ConnectionSide) : ChannelDuplexHandler() {
|
inner class Serializer(val side: ConnectionSide) : ChannelDuplexHandler() {
|
||||||
private val backlog = ByteArrayList()
|
private val backlog = ByteArrayList()
|
||||||
private var discardBytes = 0
|
private var discardBytes = 0
|
||||||
@ -105,17 +147,23 @@ class PacketRegistry(val isLegacy: Boolean) {
|
|||||||
val stream: InputStream
|
val stream: InputStream
|
||||||
|
|
||||||
if (isCompressed) {
|
if (isCompressed) {
|
||||||
stream = BufferedInputStream(InflaterInputStream(FastByteArrayInputStream(backlog.elements(), 0, backlog.size)))
|
stream = BufferedInputStream(LimitingInputStream(InflaterInputStream(FastByteArrayInputStream(backlog.elements(), 0, backlog.size))))
|
||||||
} else {
|
} else {
|
||||||
stream = FastByteArrayInputStream(backlog.elements(), 0, backlog.size)
|
stream = FastByteArrayInputStream(backlog.elements(), 0, backlog.size)
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
// legacy protocol allows to stitch multiple packets of same type together without
|
||||||
ctx.fireChannelRead(readingType!!.factory.read(DataInputStream(stream), isLegacy))
|
// separate headers for each
|
||||||
} catch (err: Throwable) {
|
while (stream.available() > 0) {
|
||||||
LOGGER.error("Error while reading incoming packet from network (type ${readingType!!.id}; ${readingType!!.type})", err)
|
try {
|
||||||
|
ctx.fireChannelRead(readingType!!.factory.read(DataInputStream(stream), isLegacy))
|
||||||
|
} catch (err: Throwable) {
|
||||||
|
LOGGER.error("Error while reading incoming packet from network (type ${readingType!!.id}; ${readingType!!.type})", err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
stream.close()
|
||||||
|
|
||||||
backlog.clear()
|
backlog.clear()
|
||||||
readingType = null
|
readingType = null
|
||||||
isCompressed = false
|
isCompressed = false
|
||||||
@ -142,6 +190,9 @@ class PacketRegistry(val isLegacy: Boolean) {
|
|||||||
} else if (!type.direction.acceptedOn(side)) {
|
} else if (!type.direction.acceptedOn(side)) {
|
||||||
LOGGER.error("Packet type $packetType (${type.type}) can not be accepted on side $side! Discarding ${dataLength.absoluteValue} bytes")
|
LOGGER.error("Packet type $packetType (${type.type}) can not be accepted on side $side! Discarding ${dataLength.absoluteValue} bytes")
|
||||||
discardBytes = dataLength.absoluteValue
|
discardBytes = dataLength.absoluteValue
|
||||||
|
} else if (dataLength.absoluteValue >= MAX_PACKET_SIZE) {
|
||||||
|
LOGGER.error("Packet ($packetType/${type.type}) of ${dataLength.absoluteValue} bytes is bigger than maximum allowed $MAX_PACKET_SIZE bytes")
|
||||||
|
discardBytes = dataLength.absoluteValue
|
||||||
} else {
|
} else {
|
||||||
LOGGER.debug("Packet type {} ({}) received on {} (size {} bytes)", packetType, type.type, side, dataLength.absoluteValue)
|
LOGGER.debug("Packet type {} ({}) received on {} (size {} bytes)", packetType, type.type, side, dataLength.absoluteValue)
|
||||||
readingType = type
|
readingType = type
|
||||||
@ -171,13 +222,39 @@ class PacketRegistry(val isLegacy: Boolean) {
|
|||||||
if (isLegacy)
|
if (isLegacy)
|
||||||
check(stream.length > 0) { "Packet $msg didn't write any data to network, this is not allowed by legacy protocol" }
|
check(stream.length > 0) { "Packet $msg didn't write any data to network, this is not allowed by legacy protocol" }
|
||||||
|
|
||||||
val buff = ctx.alloc().buffer(stream.length + 5)
|
if (stream.length >= 512) {
|
||||||
val stream2 = ByteBufOutputStream(buff)
|
// compress
|
||||||
stream2.writeByte(type.id)
|
val deflater = Deflater(3)
|
||||||
stream2.writeSignedVarInt(stream.length)
|
val buffers = ByteArrayList(1024)
|
||||||
stream2.write(stream.array, 0, stream.length)
|
val buffer = ByteArray(1024)
|
||||||
LOGGER.debug("Packet type {} ({}) sent from {} (size {} bytes)", type.id, type.type, side, stream.length)
|
deflater.setInput(stream.array, 0, stream.length)
|
||||||
ctx.write(buff, promise)
|
|
||||||
|
while (!deflater.needsInput()) {
|
||||||
|
val deflated = deflater.deflate(buffer)
|
||||||
|
|
||||||
|
if (deflated > 0)
|
||||||
|
buffers.addElements(buffers.size, buffer, 0, deflated)
|
||||||
|
else
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
val buff = ctx.alloc().buffer(buffers.size + 5)
|
||||||
|
val stream2 = ByteBufOutputStream(buff)
|
||||||
|
stream2.writeByte(type.id)
|
||||||
|
stream2.writeSignedVarInt(-buffers.size)
|
||||||
|
stream2.write(buffers.elements(), 0, buffers.size)
|
||||||
|
LOGGER.debug("Packet type {} ({}) sent from {} (size {} bytes / COMPRESSED size {} bytes)", type.id, type.type, side, stream.length, buffers.size)
|
||||||
|
ctx.write(buff, promise)
|
||||||
|
} else {
|
||||||
|
// send as-is
|
||||||
|
val buff = ctx.alloc().buffer(stream.length + 5)
|
||||||
|
val stream2 = ByteBufOutputStream(buff)
|
||||||
|
stream2.writeByte(type.id)
|
||||||
|
stream2.writeSignedVarInt(stream.length)
|
||||||
|
stream2.write(stream.array, 0, stream.length)
|
||||||
|
LOGGER.debug("Packet type {} ({}) sent from {} (size {} bytes)", type.id, type.type, side, stream.length)
|
||||||
|
ctx.write(buff, promise)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -213,6 +290,12 @@ class PacketRegistry(val isLegacy: Boolean) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
const val MAX_PACKET_SIZE = 64L * 1024L * 1024L // 64 MiB
|
||||||
|
// this includes both compressed and uncompressed
|
||||||
|
// Original game allows 16 mebibyte packets
|
||||||
|
// but it doesn't account for compression bomb (packets are fully uncompressed
|
||||||
|
// right away without limiting decompressed output size)
|
||||||
|
|
||||||
private val LOGGER = LogManager.getLogger()
|
private val LOGGER = LogManager.getLogger()
|
||||||
|
|
||||||
val NATIVE = PacketRegistry(false)
|
val NATIVE = PacketRegistry(false)
|
||||||
@ -266,16 +349,16 @@ class PacketRegistry(val isLegacy: Boolean) {
|
|||||||
|
|
||||||
// Packets sent bidirectionally between the universe client and the universe
|
// Packets sent bidirectionally between the universe client and the universe
|
||||||
// server
|
// server
|
||||||
LEGACY.skip("ClientContextUpdate")
|
LEGACY.add(ClientContextUpdatePacket::read)
|
||||||
|
|
||||||
// Packets sent world server -> world client
|
// Packets sent world server -> world client
|
||||||
LEGACY.add(::WorldStartPacket) // WorldStart
|
LEGACY.add(::WorldStartPacket) // WorldStart
|
||||||
LEGACY.skip("WorldStop")
|
LEGACY.add(::WorldStopPacket)
|
||||||
LEGACY.skip("WorldLayoutUpdate")
|
LEGACY.skip("WorldLayoutUpdate")
|
||||||
LEGACY.skip("WorldParametersUpdate")
|
LEGACY.skip("WorldParametersUpdate")
|
||||||
LEGACY.skip("CentralStructureUpdate")
|
LEGACY.skip("CentralStructureUpdate")
|
||||||
LEGACY.skip("TileArrayUpdate")
|
LEGACY.add(LegacyTileArrayUpdatePacket::read)
|
||||||
LEGACY.skip("TileUpdate")
|
LEGACY.add(LegacyTileUpdatePacket::read)
|
||||||
LEGACY.skip("TileLiquidUpdate")
|
LEGACY.skip("TileLiquidUpdate")
|
||||||
LEGACY.skip("TileDamageUpdate")
|
LEGACY.skip("TileDamageUpdate")
|
||||||
LEGACY.skip("TileModificationFailure")
|
LEGACY.skip("TileModificationFailure")
|
||||||
@ -286,7 +369,7 @@ class PacketRegistry(val isLegacy: Boolean) {
|
|||||||
LEGACY.skip("SetDungeonBreathable")
|
LEGACY.skip("SetDungeonBreathable")
|
||||||
LEGACY.skip("SetPlayerStart")
|
LEGACY.skip("SetPlayerStart")
|
||||||
LEGACY.skip("FindUniqueEntityResponse")
|
LEGACY.skip("FindUniqueEntityResponse")
|
||||||
LEGACY.skip("Pong")
|
LEGACY.add(PongPacket)
|
||||||
|
|
||||||
// Packets sent world client -> world server
|
// Packets sent world client -> world server
|
||||||
LEGACY.skip("ModifyTileList")
|
LEGACY.skip("ModifyTileList")
|
||||||
@ -299,7 +382,7 @@ class PacketRegistry(val isLegacy: Boolean) {
|
|||||||
LEGACY.skip("WorldClientStateUpdate")
|
LEGACY.skip("WorldClientStateUpdate")
|
||||||
LEGACY.skip("FindUniqueEntity")
|
LEGACY.skip("FindUniqueEntity")
|
||||||
LEGACY.skip("WorldStartAcknowledge")
|
LEGACY.skip("WorldStartAcknowledge")
|
||||||
LEGACY.skip("Ping")
|
LEGACY.add(PingPacket)
|
||||||
|
|
||||||
// Packets sent bidirectionally between world client and world server
|
// Packets sent bidirectionally between world client and world server
|
||||||
LEGACY.skip("EntityCreate")
|
LEGACY.skip("EntityCreate")
|
||||||
|
@ -0,0 +1,96 @@
|
|||||||
|
package ru.dbotthepony.kstarbound.network.packets
|
||||||
|
|
||||||
|
import it.unimi.dsi.fastutil.bytes.ByteArrayList
|
||||||
|
import it.unimi.dsi.fastutil.io.FastByteArrayInputStream
|
||||||
|
import it.unimi.dsi.fastutil.io.FastByteArrayOutputStream
|
||||||
|
import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap
|
||||||
|
import ru.dbotthepony.kommons.io.ByteKey
|
||||||
|
import ru.dbotthepony.kommons.io.readByteArray
|
||||||
|
import ru.dbotthepony.kommons.io.readByteKey
|
||||||
|
import ru.dbotthepony.kommons.io.readCollection
|
||||||
|
import ru.dbotthepony.kommons.io.readKOptional
|
||||||
|
import ru.dbotthepony.kommons.io.readMap
|
||||||
|
import ru.dbotthepony.kommons.io.writeByteArray
|
||||||
|
import ru.dbotthepony.kommons.io.writeCollection
|
||||||
|
import ru.dbotthepony.kommons.io.writeKOptional
|
||||||
|
import ru.dbotthepony.kommons.io.writeMap
|
||||||
|
import ru.dbotthepony.kommons.io.writeVarInt
|
||||||
|
import ru.dbotthepony.kommons.util.KOptional
|
||||||
|
import ru.dbotthepony.kstarbound.client.ClientConnection
|
||||||
|
import ru.dbotthepony.kstarbound.network.IClientPacket
|
||||||
|
import ru.dbotthepony.kstarbound.network.IServerPacket
|
||||||
|
import ru.dbotthepony.kstarbound.network.JsonRPC
|
||||||
|
import ru.dbotthepony.kstarbound.server.ServerConnection
|
||||||
|
import java.io.DataInputStream
|
||||||
|
import java.io.DataOutputStream
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Embeds RPC (remote procedure call) data for setting various variables on other side
|
||||||
|
*/
|
||||||
|
class ClientContextUpdatePacket(
|
||||||
|
val rpcEntries: List<JsonRPC.Entry>,
|
||||||
|
val shipChunks: KOptional<Map<ByteKey, KOptional<ByteArray>>>,
|
||||||
|
val networkedVars: KOptional<ByteArrayList>
|
||||||
|
) : IClientPacket, IServerPacket {
|
||||||
|
override fun write(stream: DataOutputStream, isLegacy: Boolean) {
|
||||||
|
if (isLegacy) {
|
||||||
|
// this is stupid
|
||||||
|
run {
|
||||||
|
val wrap = FastByteArrayOutputStream()
|
||||||
|
DataOutputStream(wrap).writeCollection(rpcEntries) { it.write(this, true) }
|
||||||
|
stream.writeVarInt(wrap.length)
|
||||||
|
stream.write(wrap.array, 0, wrap.length)
|
||||||
|
}
|
||||||
|
|
||||||
|
shipChunks.ifPresent {
|
||||||
|
val wrap = FastByteArrayOutputStream()
|
||||||
|
DataOutputStream(wrap).writeMap(it, { it.write(this) }, { writeKOptional(it) { writeByteArray(it) } })
|
||||||
|
stream.writeByteArray(wrap.array, 0, wrap.length)
|
||||||
|
}
|
||||||
|
|
||||||
|
networkedVars.ifPresent {
|
||||||
|
stream.writeVarInt(it.size)
|
||||||
|
stream.write(it.elements(), 0, it.size)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
stream.writeCollection(rpcEntries) { it.write(this, false) }
|
||||||
|
|
||||||
|
stream.writeKOptional(shipChunks) {
|
||||||
|
writeMap(it, { it.write(this) }, { writeKOptional(it) { writeByteArray(it) } })
|
||||||
|
}
|
||||||
|
|
||||||
|
stream.writeKOptional(networkedVars) {
|
||||||
|
writeVarInt(it.size)
|
||||||
|
write(it.elements(), 0, it.size)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun play(connection: ServerConnection) {
|
||||||
|
connection.rpc.read(rpcEntries)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun play(connection: ClientConnection) {
|
||||||
|
connection.rpc.read(rpcEntries)
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
fun read(stream: DataInputStream, isLegacy: Boolean): ClientContextUpdatePacket {
|
||||||
|
if (isLegacy) {
|
||||||
|
// beyond stupid
|
||||||
|
val rpc = stream.readByteArray()
|
||||||
|
|
||||||
|
return ClientContextUpdatePacket(
|
||||||
|
DataInputStream(FastByteArrayInputStream(rpc)).readCollection { JsonRPC.Entry.legacy(this) },
|
||||||
|
if (stream.available() > 0) KOptional(stream.readMap({ readByteKey() }, { readKOptional { readByteArray() } })) else KOptional(),
|
||||||
|
if (stream.available() > 0) KOptional(ByteArrayList.wrap(stream.readByteArray())) else KOptional(),
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
return ClientContextUpdatePacket(
|
||||||
|
stream.readCollection { JsonRPC.Entry.native(this) },
|
||||||
|
stream.readKOptional { readMap({ readByteKey() }, { readKOptional { readByteArray() } }) },
|
||||||
|
stream.readKOptional { ByteArrayList.wrap(readByteArray()) })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,27 @@
|
|||||||
|
package ru.dbotthepony.kstarbound.network.packets
|
||||||
|
|
||||||
|
import ru.dbotthepony.kstarbound.client.ClientConnection
|
||||||
|
import ru.dbotthepony.kstarbound.network.IClientPacket
|
||||||
|
import ru.dbotthepony.kstarbound.network.IServerPacket
|
||||||
|
import ru.dbotthepony.kstarbound.server.ServerConnection
|
||||||
|
import java.io.DataOutputStream
|
||||||
|
|
||||||
|
object PongPacket : IClientPacket {
|
||||||
|
override fun write(stream: DataOutputStream, isLegacy: Boolean) {
|
||||||
|
if (isLegacy) stream.write(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun play(connection: ClientConnection) {
|
||||||
|
TODO("Not yet implemented")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
object PingPacket : IServerPacket {
|
||||||
|
override fun write(stream: DataOutputStream, isLegacy: Boolean) {
|
||||||
|
if (isLegacy) stream.write(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun play(connection: ServerConnection) {
|
||||||
|
connection.send(PongPacket)
|
||||||
|
}
|
||||||
|
}
|
@ -1,12 +1,10 @@
|
|||||||
package ru.dbotthepony.kstarbound.network.packets
|
package ru.dbotthepony.kstarbound.network.packets
|
||||||
|
|
||||||
import ru.dbotthepony.kstarbound.Starbound
|
import ru.dbotthepony.kstarbound.Starbound
|
||||||
import ru.dbotthepony.kstarbound.defs.CelestialBaseInformation
|
|
||||||
import ru.dbotthepony.kstarbound.network.IServerPacket
|
import ru.dbotthepony.kstarbound.network.IServerPacket
|
||||||
import ru.dbotthepony.kstarbound.server.ServerConnection
|
import ru.dbotthepony.kstarbound.server.ServerConnection
|
||||||
import java.io.DataInputStream
|
import java.io.DataInputStream
|
||||||
import java.io.DataOutputStream
|
import java.io.DataOutputStream
|
||||||
import java.util.UUID
|
|
||||||
|
|
||||||
data class ProtocolRequestPacket(val version: Int) : IServerPacket {
|
data class ProtocolRequestPacket(val version: Int) : IServerPacket {
|
||||||
constructor(stream: DataInputStream, isLegacy: Boolean) : this(stream.readInt())
|
constructor(stream: DataInputStream, isLegacy: Boolean) : this(stream.readInt())
|
||||||
|
@ -5,7 +5,7 @@ import ru.dbotthepony.kommons.io.readVarInt
|
|||||||
import ru.dbotthepony.kommons.io.writeUUID
|
import ru.dbotthepony.kommons.io.writeUUID
|
||||||
import ru.dbotthepony.kommons.io.writeVarInt
|
import ru.dbotthepony.kommons.io.writeVarInt
|
||||||
import ru.dbotthepony.kstarbound.client.ClientConnection
|
import ru.dbotthepony.kstarbound.client.ClientConnection
|
||||||
import ru.dbotthepony.kstarbound.defs.CelestialBaseInformation
|
import ru.dbotthepony.kstarbound.defs.world.CelestialBaseInformation
|
||||||
import ru.dbotthepony.kstarbound.network.IClientPacket
|
import ru.dbotthepony.kstarbound.network.IClientPacket
|
||||||
import java.io.DataInputStream
|
import java.io.DataInputStream
|
||||||
import java.io.DataOutputStream
|
import java.io.DataOutputStream
|
||||||
|
@ -0,0 +1,76 @@
|
|||||||
|
package ru.dbotthepony.kstarbound.network.packets.clientbound
|
||||||
|
|
||||||
|
import ru.dbotthepony.kommons.arrays.Object2DArray
|
||||||
|
import ru.dbotthepony.kommons.io.readSignedVarInt
|
||||||
|
import ru.dbotthepony.kommons.io.readVarInt
|
||||||
|
import ru.dbotthepony.kommons.io.readVector2i
|
||||||
|
import ru.dbotthepony.kommons.io.writeSignedVarInt
|
||||||
|
import ru.dbotthepony.kommons.io.writeStruct2i
|
||||||
|
import ru.dbotthepony.kommons.io.writeVarInt
|
||||||
|
import ru.dbotthepony.kommons.vector.Vector2i
|
||||||
|
import ru.dbotthepony.kstarbound.client.ClientConnection
|
||||||
|
import ru.dbotthepony.kstarbound.network.IClientPacket
|
||||||
|
import ru.dbotthepony.kstarbound.network.LegacyNetworkCellState
|
||||||
|
import ru.dbotthepony.kstarbound.world.Chunk
|
||||||
|
import java.io.DataInputStream
|
||||||
|
import java.io.DataOutputStream
|
||||||
|
|
||||||
|
class LegacyTileUpdatePacket(val position: Vector2i, val tile: LegacyNetworkCellState) : IClientPacket {
|
||||||
|
override fun write(stream: DataOutputStream, isLegacy: Boolean) {
|
||||||
|
stream.writeSignedVarInt(position.x)
|
||||||
|
stream.writeSignedVarInt(position.y)
|
||||||
|
tile.write(stream)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun play(connection: ClientConnection) {
|
||||||
|
TODO("Not yet implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
fun read(stream: DataInputStream, isLegacy: Boolean): LegacyTileUpdatePacket {
|
||||||
|
check(isLegacy) { "Using legacy packet in native protocol" }
|
||||||
|
return LegacyTileUpdatePacket(Vector2i(stream.readSignedVarInt(), stream.readSignedVarInt()), LegacyNetworkCellState.read(stream))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class LegacyTileArrayUpdatePacket(val origin: Vector2i, val data: Object2DArray<LegacyNetworkCellState>) : IClientPacket {
|
||||||
|
constructor(chunk: Chunk<*, *>) : this(chunk.pos.tile, chunk.legacyNetworkCells())
|
||||||
|
|
||||||
|
override fun write(stream: DataOutputStream, isLegacy: Boolean) {
|
||||||
|
stream.writeSignedVarInt(origin.x)
|
||||||
|
stream.writeSignedVarInt(origin.y)
|
||||||
|
stream.writeVarInt(data.rows)
|
||||||
|
stream.writeVarInt(data.columns)
|
||||||
|
|
||||||
|
for (y in data.columnIndices) {
|
||||||
|
for (x in data.rowIndices) {
|
||||||
|
data[y, x].write(stream)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun play(connection: ClientConnection) {
|
||||||
|
TODO("Not yet implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
fun read(stream: DataInputStream, isLegacy: Boolean): LegacyTileArrayUpdatePacket {
|
||||||
|
check(isLegacy) { "Using legacy packet in native protocol" }
|
||||||
|
val origin = Vector2i(stream.readSignedVarInt(), stream.readSignedVarInt())
|
||||||
|
|
||||||
|
val rows = stream.readVarInt()
|
||||||
|
val columns = stream.readVarInt()
|
||||||
|
|
||||||
|
val data = Object2DArray.nulls<LegacyNetworkCellState>(columns, rows)
|
||||||
|
|
||||||
|
for (y in data.columnIndices) {
|
||||||
|
for (x in data.rowIndices) {
|
||||||
|
data[y, x] = LegacyNetworkCellState.read(stream)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return LegacyTileArrayUpdatePacket(origin, data as Object2DArray<LegacyNetworkCellState>)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,20 @@
|
|||||||
|
package ru.dbotthepony.kstarbound.network.packets.clientbound
|
||||||
|
|
||||||
|
import ru.dbotthepony.kommons.io.readBinaryString
|
||||||
|
import ru.dbotthepony.kommons.io.writeBinaryString
|
||||||
|
import ru.dbotthepony.kstarbound.client.ClientConnection
|
||||||
|
import ru.dbotthepony.kstarbound.network.IClientPacket
|
||||||
|
import java.io.DataInputStream
|
||||||
|
import java.io.DataOutputStream
|
||||||
|
|
||||||
|
class WorldStopPacket(val reason: String = "") : IClientPacket {
|
||||||
|
constructor(stream: DataInputStream, isLegacy: Boolean) : this(stream.readBinaryString())
|
||||||
|
|
||||||
|
override fun write(stream: DataOutputStream, isLegacy: Boolean) {
|
||||||
|
stream.writeBinaryString(reason)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun play(connection: ClientConnection) {
|
||||||
|
TODO("Not yet implemented")
|
||||||
|
}
|
||||||
|
}
|
@ -15,7 +15,7 @@ import ru.dbotthepony.kommons.io.writeKOptional
|
|||||||
import ru.dbotthepony.kommons.io.writeMap
|
import ru.dbotthepony.kommons.io.writeMap
|
||||||
import ru.dbotthepony.kommons.io.writeUUID
|
import ru.dbotthepony.kommons.io.writeUUID
|
||||||
import ru.dbotthepony.kommons.util.KOptional
|
import ru.dbotthepony.kommons.util.KOptional
|
||||||
import ru.dbotthepony.kstarbound.defs.CelestialBaseInformation
|
import ru.dbotthepony.kstarbound.defs.world.CelestialBaseInformation
|
||||||
import ru.dbotthepony.kstarbound.defs.player.ShipUpgrades
|
import ru.dbotthepony.kstarbound.defs.player.ShipUpgrades
|
||||||
import ru.dbotthepony.kstarbound.network.IServerPacket
|
import ru.dbotthepony.kstarbound.network.IServerPacket
|
||||||
import ru.dbotthepony.kstarbound.network.packets.clientbound.ConnectSuccessPacket
|
import ru.dbotthepony.kstarbound.network.packets.clientbound.ConnectSuccessPacket
|
||||||
@ -58,7 +58,9 @@ data class ClientConnectPacket(
|
|||||||
|
|
||||||
override fun play(connection: ServerConnection) {
|
override fun play(connection: ServerConnection) {
|
||||||
LOGGER.info("Client connection request received from ${connection.channel.remoteAddress()}, Player $playerName/$playerUuid (account '$account')")
|
LOGGER.info("Client connection request received from ${connection.channel.remoteAddress()}, Player $playerName/$playerUuid (account '$account')")
|
||||||
connection.sendAndFlush(ConnectSuccessPacket(4, UUID(4L, 4L), CelestialBaseInformation()))
|
connection.receiveShipChunks(shipChunks)
|
||||||
|
connection.sendAndFlush(ConnectSuccessPacket(connection.connectionID, UUID(4L, 4L), CelestialBaseInformation()))
|
||||||
|
connection.inGame()
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
@ -1,11 +1,15 @@
|
|||||||
package ru.dbotthepony.kstarbound.server
|
package ru.dbotthepony.kstarbound.server
|
||||||
|
|
||||||
import io.netty.channel.ChannelHandlerContext
|
import io.netty.channel.ChannelHandlerContext
|
||||||
|
import it.unimi.dsi.fastutil.bytes.ByteArrayList
|
||||||
import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap
|
import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap
|
||||||
import it.unimi.dsi.fastutil.objects.ObjectLinkedOpenHashSet
|
import it.unimi.dsi.fastutil.objects.ObjectLinkedOpenHashSet
|
||||||
import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet
|
import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet
|
||||||
import org.apache.logging.log4j.LogManager
|
import org.apache.logging.log4j.LogManager
|
||||||
|
import ru.dbotthepony.kommons.io.ByteKey
|
||||||
|
import ru.dbotthepony.kommons.util.KOptional
|
||||||
import ru.dbotthepony.kommons.vector.Vector2d
|
import ru.dbotthepony.kommons.vector.Vector2d
|
||||||
|
import ru.dbotthepony.kommons.vector.Vector2i
|
||||||
import ru.dbotthepony.kstarbound.client.network.packets.ForgetChunkPacket
|
import ru.dbotthepony.kstarbound.client.network.packets.ForgetChunkPacket
|
||||||
import ru.dbotthepony.kstarbound.client.network.packets.ChunkCellsPacket
|
import ru.dbotthepony.kstarbound.client.network.packets.ChunkCellsPacket
|
||||||
import ru.dbotthepony.kstarbound.client.network.packets.ForgetEntityPacket
|
import ru.dbotthepony.kstarbound.client.network.packets.ForgetEntityPacket
|
||||||
@ -14,16 +18,34 @@ import ru.dbotthepony.kstarbound.network.Connection
|
|||||||
import ru.dbotthepony.kstarbound.network.ConnectionSide
|
import ru.dbotthepony.kstarbound.network.ConnectionSide
|
||||||
import ru.dbotthepony.kstarbound.network.ConnectionType
|
import ru.dbotthepony.kstarbound.network.ConnectionType
|
||||||
import ru.dbotthepony.kstarbound.network.IServerPacket
|
import ru.dbotthepony.kstarbound.network.IServerPacket
|
||||||
|
import ru.dbotthepony.kstarbound.network.packets.ClientContextUpdatePacket
|
||||||
|
import ru.dbotthepony.kstarbound.network.packets.clientbound.LegacyTileArrayUpdatePacket
|
||||||
|
import ru.dbotthepony.kstarbound.server.world.IChunkSource
|
||||||
|
import ru.dbotthepony.kstarbound.server.world.LegacyChunkSource
|
||||||
import ru.dbotthepony.kstarbound.server.world.ServerWorld
|
import ru.dbotthepony.kstarbound.server.world.ServerWorld
|
||||||
import ru.dbotthepony.kstarbound.world.ChunkPos
|
import ru.dbotthepony.kstarbound.world.ChunkPos
|
||||||
import ru.dbotthepony.kstarbound.world.IChunkListener
|
import ru.dbotthepony.kstarbound.world.IChunkListener
|
||||||
|
import ru.dbotthepony.kstarbound.world.WorldGeometry
|
||||||
import ru.dbotthepony.kstarbound.world.api.ImmutableCell
|
import ru.dbotthepony.kstarbound.world.api.ImmutableCell
|
||||||
import ru.dbotthepony.kstarbound.world.entities.AbstractEntity
|
import ru.dbotthepony.kstarbound.world.entities.AbstractEntity
|
||||||
import ru.dbotthepony.kstarbound.world.entities.WorldObject
|
import ru.dbotthepony.kstarbound.world.entities.WorldObject
|
||||||
import java.util.*
|
import kotlin.properties.Delegates
|
||||||
|
|
||||||
class ServerConnection(val server: StarboundServer, type: ConnectionType) : Connection(ConnectionSide.SERVER, type, UUID(0L, 0L)) {
|
// serverside part of connection
|
||||||
|
class ServerConnection(val server: StarboundServer, type: ConnectionType) : Connection(ConnectionSide.SERVER, type) {
|
||||||
var world: ServerWorld? = null
|
var world: ServerWorld? = null
|
||||||
|
lateinit var shipWorld: ServerWorld
|
||||||
|
private set
|
||||||
|
|
||||||
|
init {
|
||||||
|
connectionID = server.nextConnectionID.incrementAndGet()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun toString(): String {
|
||||||
|
val channel = if (hasChannel) channel.remoteAddress().toString() else "<no channel>"
|
||||||
|
val ship = if (::shipWorld.isInitialized) shipWorld.toString() else "<no shipworld>"
|
||||||
|
return "ServerConnection[ID=$connectionID channel=$channel / $ship]"
|
||||||
|
}
|
||||||
|
|
||||||
var trackedPosition: Vector2d = Vector2d.ZERO
|
var trackedPosition: Vector2d = Vector2d.ZERO
|
||||||
set(value) {
|
set(value) {
|
||||||
@ -52,6 +74,25 @@ class ServerConnection(val server: StarboundServer, type: ConnectionType) : Conn
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private val shipChunks = Object2ObjectOpenHashMap<ByteKey, KOptional<ByteArray>>()
|
||||||
|
private val modifiedShipChunks = ObjectOpenHashSet<ByteKey>()
|
||||||
|
var shipChunkSource by Delegates.notNull<IChunkSource>()
|
||||||
|
private set
|
||||||
|
|
||||||
|
override fun setupLegacy() {
|
||||||
|
super.setupLegacy()
|
||||||
|
shipChunkSource = LegacyChunkSource.memory(shipChunks)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun setupNative() {
|
||||||
|
super.setupNative()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun receiveShipChunks(chunks: Map<ByteKey, KOptional<ByteArray>>) {
|
||||||
|
check(shipChunks.isEmpty()) { "Already has ship chunks" }
|
||||||
|
shipChunks.putAll(chunks)
|
||||||
|
}
|
||||||
|
|
||||||
private val tickets = Object2ObjectOpenHashMap<ChunkPos, ServerWorld.ITicket>()
|
private val tickets = Object2ObjectOpenHashMap<ChunkPos, ServerWorld.ITicket>()
|
||||||
private val pendingSend = ObjectLinkedOpenHashSet<ChunkPos>()
|
private val pendingSend = ObjectLinkedOpenHashSet<ChunkPos>()
|
||||||
|
|
||||||
@ -72,12 +113,35 @@ class ServerConnection(val server: StarboundServer, type: ConnectionType) : Conn
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun flush() {
|
||||||
|
val entries = rpc.write()
|
||||||
|
|
||||||
|
if (entries != null || modifiedShipChunks.isNotEmpty()) {
|
||||||
|
channel.write(ClientContextUpdatePacket(
|
||||||
|
entries ?: listOf(),
|
||||||
|
KOptional(modifiedShipChunks.associateWith { shipChunks[it]!! }),
|
||||||
|
KOptional(ByteArrayList())))
|
||||||
|
|
||||||
|
modifiedShipChunks.clear()
|
||||||
|
}
|
||||||
|
|
||||||
|
super.flush()
|
||||||
|
}
|
||||||
|
|
||||||
fun onLeaveWorld() {
|
fun onLeaveWorld() {
|
||||||
tickets.values.forEach { it.cancel() }
|
tickets.values.forEach { it.cancel() }
|
||||||
tickets.clear()
|
tickets.clear()
|
||||||
pendingSend.clear()
|
pendingSend.clear()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun onChannelClosed() {
|
||||||
|
super.onChannelClosed()
|
||||||
|
|
||||||
|
if (::shipWorld.isInitialized) {
|
||||||
|
shipWorld.close()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private fun recomputeTrackedChunks() {
|
private fun recomputeTrackedChunks() {
|
||||||
val world = world ?: return
|
val world = world ?: return
|
||||||
val trackedPositionChunk = world.geometry.chunkFromCell(trackedPosition)
|
val trackedPositionChunk = world.geometry.chunkFromCell(trackedPosition)
|
||||||
@ -129,7 +193,13 @@ class ServerConnection(val server: StarboundServer, type: ConnectionType) : Conn
|
|||||||
|
|
||||||
for (pos in itr) {
|
for (pos in itr) {
|
||||||
val chunk = world.chunkMap[pos] ?: continue
|
val chunk = world.chunkMap[pos] ?: continue
|
||||||
send(ChunkCellsPacket(chunk))
|
|
||||||
|
if (isLegacy) {
|
||||||
|
send(LegacyTileArrayUpdatePacket(chunk))
|
||||||
|
} else {
|
||||||
|
send(ChunkCellsPacket(chunk))
|
||||||
|
}
|
||||||
|
|
||||||
itr.remove()
|
itr.remove()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -149,7 +219,13 @@ class ServerConnection(val server: StarboundServer, type: ConnectionType) : Conn
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun inGame() {
|
override fun inGame() {
|
||||||
server.playerInGame(this)
|
// server.playerInGame(this)
|
||||||
|
|
||||||
|
LOGGER.info("Initializing ship world for $this")
|
||||||
|
shipWorld = ServerWorld(server, WorldGeometry(Vector2i(2048, 2048), false, false))
|
||||||
|
shipWorld.addChunkSource(shipChunkSource)
|
||||||
|
shipWorld.thread.start()
|
||||||
|
shipWorld.acceptPlayer(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
@ -31,6 +31,8 @@ sealed class StarboundServer(val root: File) : Closeable {
|
|||||||
val thread = Thread(spinner, "Starbound Server $serverID")
|
val thread = Thread(spinner, "Starbound Server $serverID")
|
||||||
val universe = ServerUniverse()
|
val universe = ServerUniverse()
|
||||||
|
|
||||||
|
val nextConnectionID = AtomicInteger()
|
||||||
|
|
||||||
val settings = ServerSettings()
|
val settings = ServerSettings()
|
||||||
val channels = ServerChannels(this)
|
val channels = ServerChannels(this)
|
||||||
val lock = ReentrantLock()
|
val lock = ReentrantLock()
|
||||||
|
@ -22,20 +22,21 @@ import java.io.ByteArrayInputStream
|
|||||||
import java.io.DataInputStream
|
import java.io.DataInputStream
|
||||||
import java.util.concurrent.CompletableFuture
|
import java.util.concurrent.CompletableFuture
|
||||||
import java.util.function.Supplier
|
import java.util.function.Supplier
|
||||||
import java.util.zip.Inflater
|
|
||||||
import java.util.zip.InflaterInputStream
|
import java.util.zip.InflaterInputStream
|
||||||
|
|
||||||
class LegacyChunkSource(val db: BTreeDB5) : IChunkSource {
|
class LegacyChunkSource(val loader: Loader) : IChunkSource {
|
||||||
private val carrier = CarriedExecutor(Starbound.STORAGE_IO_POOL)
|
fun interface Loader {
|
||||||
|
operator fun invoke(at: ByteKey): CompletableFuture<KOptional<ByteArray>>
|
||||||
|
}
|
||||||
|
|
||||||
override fun getTiles(pos: ChunkPos): CompletableFuture<KOptional<Object2DArray<out AbstractCell>>> {
|
override fun getTiles(pos: ChunkPos): CompletableFuture<KOptional<Object2DArray<out AbstractCell>>> {
|
||||||
val chunkX = pos.x
|
val chunkX = pos.x
|
||||||
val chunkY = pos.y
|
val chunkY = pos.y
|
||||||
val key = ByteKey(1, (chunkX shr 8).toByte(), chunkX.toByte(), (chunkY shr 8).toByte(), chunkY.toByte())
|
val key = ByteKey(1, (chunkX shr 8).toByte(), chunkX.toByte(), (chunkY shr 8).toByte(), chunkY.toByte())
|
||||||
|
|
||||||
return CompletableFuture.supplyAsync(Supplier { db.read(key) }, carrier).thenApplyAsync {
|
return loader(key).thenApplyAsync {
|
||||||
it.map {
|
it.map {
|
||||||
val reader = DataInputStream(BufferedInputStream(InflaterInputStream(ByteArrayInputStream(it), Inflater())))
|
val reader = DataInputStream(BufferedInputStream(InflaterInputStream(ByteArrayInputStream(it))))
|
||||||
reader.skipBytes(3)
|
reader.skipBytes(3)
|
||||||
|
|
||||||
val result = Object2DArray.nulls<ImmutableCell>(CHUNK_SIZE, CHUNK_SIZE)
|
val result = Object2DArray.nulls<ImmutableCell>(CHUNK_SIZE, CHUNK_SIZE)
|
||||||
@ -46,6 +47,7 @@ class LegacyChunkSource(val db: BTreeDB5) : IChunkSource {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
reader.close()
|
||||||
result as Object2DArray<out AbstractCell>
|
result as Object2DArray<out AbstractCell>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -56,9 +58,9 @@ class LegacyChunkSource(val db: BTreeDB5) : IChunkSource {
|
|||||||
val chunkY = pos.y
|
val chunkY = pos.y
|
||||||
val key = ByteKey(2, (chunkX shr 8).toByte(), chunkX.toByte(), (chunkY shr 8).toByte(), chunkY.toByte())
|
val key = ByteKey(2, (chunkX shr 8).toByte(), chunkX.toByte(), (chunkY shr 8).toByte(), chunkY.toByte())
|
||||||
|
|
||||||
return CompletableFuture.supplyAsync(Supplier { db.read(key) }, carrier).thenApplyAsync {
|
return loader(key).thenApplyAsync {
|
||||||
it.map {
|
it.map {
|
||||||
val reader = DataInputStream(BufferedInputStream(InflaterInputStream(ByteArrayInputStream(it), Inflater())))
|
val reader = DataInputStream(BufferedInputStream(InflaterInputStream(ByteArrayInputStream(it))))
|
||||||
val i = reader.readVarInt()
|
val i = reader.readVarInt()
|
||||||
val objects = ArrayList<AbstractEntity>()
|
val objects = ArrayList<AbstractEntity>()
|
||||||
|
|
||||||
@ -76,6 +78,7 @@ class LegacyChunkSource(val db: BTreeDB5) : IChunkSource {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
reader.close()
|
||||||
objects
|
objects
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -84,5 +87,15 @@ class LegacyChunkSource(val db: BTreeDB5) : IChunkSource {
|
|||||||
companion object {
|
companion object {
|
||||||
private val vectors by lazy { Starbound.gson.getAdapter(Vector2i::class.java) }
|
private val vectors by lazy { Starbound.gson.getAdapter(Vector2i::class.java) }
|
||||||
private val LOGGER = LogManager.getLogger()
|
private val LOGGER = LogManager.getLogger()
|
||||||
|
|
||||||
|
fun file(file: BTreeDB5): LegacyChunkSource {
|
||||||
|
val carrier = CarriedExecutor(Starbound.IO_EXECUTOR)
|
||||||
|
val loader = Loader { key -> CompletableFuture.supplyAsync(Supplier { file.read(key) }, carrier) }
|
||||||
|
return LegacyChunkSource(loader)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun memory(backing: Map<ByteKey, KOptional<ByteArray>>): LegacyChunkSource {
|
||||||
|
return LegacyChunkSource { key -> CompletableFuture.completedFuture(backing[key] ?: KOptional()) }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -7,29 +7,27 @@ import com.google.gson.stream.JsonReader
|
|||||||
import it.unimi.dsi.fastutil.io.FastByteArrayInputStream
|
import it.unimi.dsi.fastutil.io.FastByteArrayInputStream
|
||||||
import it.unimi.dsi.fastutil.objects.ObjectArrayList
|
import it.unimi.dsi.fastutil.objects.ObjectArrayList
|
||||||
import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet
|
import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet
|
||||||
|
import kotlinx.coroutines.future.await
|
||||||
import ru.dbotthepony.kommons.collect.chainOptionalFutures
|
import ru.dbotthepony.kommons.collect.chainOptionalFutures
|
||||||
import ru.dbotthepony.kommons.gson.get
|
import ru.dbotthepony.kommons.gson.get
|
||||||
import ru.dbotthepony.kommons.io.BTreeDB6
|
import ru.dbotthepony.kommons.io.BTreeDB6
|
||||||
import ru.dbotthepony.kommons.util.AABBi
|
import ru.dbotthepony.kommons.util.AABBi
|
||||||
import ru.dbotthepony.kommons.util.IStruct2i
|
|
||||||
import ru.dbotthepony.kommons.util.KOptional
|
import ru.dbotthepony.kommons.util.KOptional
|
||||||
import ru.dbotthepony.kommons.vector.Vector2i
|
import ru.dbotthepony.kommons.vector.Vector2i
|
||||||
import ru.dbotthepony.kstarbound.Starbound
|
import ru.dbotthepony.kstarbound.Starbound
|
||||||
import ru.dbotthepony.kstarbound.defs.CelestialBaseInformation
|
import ru.dbotthepony.kstarbound.defs.world.CelestialBaseInformation
|
||||||
import ru.dbotthepony.kstarbound.defs.CelestialGenerationInformation
|
import ru.dbotthepony.kstarbound.defs.world.CelestialGenerationInformation
|
||||||
import ru.dbotthepony.kstarbound.defs.CelestialNames
|
import ru.dbotthepony.kstarbound.defs.world.CelestialNames
|
||||||
|
import ru.dbotthepony.kstarbound.defs.world.CelestialParameters
|
||||||
import ru.dbotthepony.kstarbound.fromJson
|
import ru.dbotthepony.kstarbound.fromJson
|
||||||
import ru.dbotthepony.kstarbound.io.BTreeDB5
|
import ru.dbotthepony.kstarbound.io.BTreeDB5
|
||||||
import ru.dbotthepony.kstarbound.util.random.staticRandom64
|
import ru.dbotthepony.kstarbound.util.random.staticRandom64
|
||||||
import ru.dbotthepony.kstarbound.world.CoordinateMapper
|
|
||||||
import ru.dbotthepony.kstarbound.world.Universe
|
import ru.dbotthepony.kstarbound.world.Universe
|
||||||
import ru.dbotthepony.kstarbound.world.UniversePos
|
import ru.dbotthepony.kstarbound.world.UniversePos
|
||||||
import ru.dbotthepony.kstarbound.world.positiveModulo
|
|
||||||
import java.io.Closeable
|
import java.io.Closeable
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.io.InputStreamReader
|
import java.io.InputStreamReader
|
||||||
import java.time.Duration
|
import java.time.Duration
|
||||||
import java.util.*
|
|
||||||
import java.util.concurrent.CompletableFuture
|
import java.util.concurrent.CompletableFuture
|
||||||
import kotlin.collections.ArrayList
|
import kotlin.collections.ArrayList
|
||||||
|
|
||||||
@ -69,18 +67,39 @@ class ServerUniverse private constructor(marker: Nothing?) : Universe(), Closeab
|
|||||||
private val sources = ArrayList<UniverseSource>()
|
private val sources = ArrayList<UniverseSource>()
|
||||||
private val closeables = ArrayList<Closeable>()
|
private val closeables = ArrayList<Closeable>()
|
||||||
|
|
||||||
override fun name(pos: UniversePos): CompletableFuture<KOptional<String>> {
|
override suspend fun parameters(pos: UniversePos): CelestialParameters? {
|
||||||
return getChunk(pos).thenApply {
|
return getChunk(pos)?.parameters(pos)
|
||||||
it.flatMap { it.parameters(pos) }.map { it.name }
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun scanSystems(region: AABBi, includedTypes: Set<String>?): CompletableFuture<List<UniversePos>> {
|
override suspend fun hasChildren(pos: UniversePos): Boolean {
|
||||||
|
val system = getChunk(pos)?.systems?.get(pos.location) ?: return false
|
||||||
|
|
||||||
|
if (pos.isSystem)
|
||||||
|
return system.planets.isNotEmpty()
|
||||||
|
else if (pos.isPlanet)
|
||||||
|
return system.planets[pos.orbitNumber]?.satellites?.isNotEmpty() ?: false
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun children(pos: UniversePos): List<UniversePos> {
|
||||||
|
val chunk = getChunk(pos) ?: return emptyList()
|
||||||
|
val system = chunk.systems[pos.location] ?: return listOf()
|
||||||
|
|
||||||
|
if (pos.isSystem)
|
||||||
|
return system.planets.keys.intStream().mapToObj { UniversePos(pos.location, it) }.toList()
|
||||||
|
else if (pos.isPlanet)
|
||||||
|
return system.planets[pos.planetOrbit]?.satellites?.keys?.intStream()?.mapToObj { UniversePos(pos.location, pos.planetOrbit, it) }?.toList() ?: listOf()
|
||||||
|
|
||||||
|
return listOf()
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun scanSystems(region: AABBi, includedTypes: Set<String>?): List<UniversePos> {
|
||||||
val copy = if (includedTypes != null) ObjectOpenHashSet(includedTypes) else null
|
val copy = if (includedTypes != null) ObjectOpenHashSet(includedTypes) else null
|
||||||
val futures = ArrayList<CompletableFuture<List<UniversePos>>>()
|
val futures = ArrayList<CompletableFuture<List<UniversePos>>>()
|
||||||
|
|
||||||
for (pos in chunkPositions(region)) {
|
for (pos in chunkPositions(region)) {
|
||||||
val f = getChunk(pos).thenApply {
|
val f = getChunkFuture(pos).thenApply {
|
||||||
it.map<List<UniversePos>> {
|
it.map<List<UniversePos>> {
|
||||||
val result = ArrayList<UniversePos>()
|
val result = ArrayList<UniversePos>()
|
||||||
|
|
||||||
@ -103,23 +122,23 @@ class ServerUniverse private constructor(marker: Nothing?) : Universe(), Closeab
|
|||||||
futures.add(f)
|
futures.add(f)
|
||||||
}
|
}
|
||||||
|
|
||||||
return CompletableFuture.allOf(*futures.toTypedArray())
|
CompletableFuture.allOf(*futures.toTypedArray()).await()
|
||||||
.thenApply { futures.stream().flatMap { it.get().stream() }.toList() }
|
return futures.stream().flatMap { it.get().stream() }.toList()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun scanConstellationLines(region: AABBi): CompletableFuture<List<Pair<Vector2i, Vector2i>>> {
|
override suspend fun scanConstellationLines(region: AABBi): List<Pair<Vector2i, Vector2i>> {
|
||||||
val futures = ArrayList<CompletableFuture<List<Pair<Vector2i, Vector2i>>>>()
|
val futures = ArrayList<CompletableFuture<List<Pair<Vector2i, Vector2i>>>>()
|
||||||
|
|
||||||
for (pos in chunkPositions(region)) {
|
for (pos in chunkPositions(region)) {
|
||||||
val f = getChunk(pos).thenApply {
|
val f = getChunkFuture(pos).thenApply {
|
||||||
it.map<List<Pair<Vector2i, Vector2i>>> { ObjectArrayList(it.constellations) }.orElse(listOf())
|
it.map<List<Pair<Vector2i, Vector2i>>> { ObjectArrayList(it.constellations) }.orElse(listOf())
|
||||||
}
|
}
|
||||||
|
|
||||||
futures.add(f)
|
futures.add(f)
|
||||||
}
|
}
|
||||||
|
|
||||||
return CompletableFuture.allOf(*futures.toTypedArray())
|
CompletableFuture.allOf(*futures.toTypedArray()).await()
|
||||||
.thenApply { futures.stream().flatMap { it.get().stream() }.toList() }
|
return futures.stream().flatMap { it.get().stream() }.toList()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun scanRegionFullyLoaded(region: AABBi): Boolean {
|
override fun scanRegionFullyLoaded(region: AABBi): Boolean {
|
||||||
@ -140,12 +159,22 @@ class ServerUniverse private constructor(marker: Nothing?) : Universe(), Closeab
|
|||||||
.scheduler(Scheduler.systemScheduler())
|
.scheduler(Scheduler.systemScheduler())
|
||||||
.build()
|
.build()
|
||||||
|
|
||||||
fun getChunk(pos: UniversePos): CompletableFuture<KOptional<UniverseChunk>> {
|
fun getChunkFuture(pos: Vector2i): CompletableFuture<KOptional<UniverseChunk>> {
|
||||||
|
return chunkCache.get(pos) { p -> chainOptionalFutures(sources) { it.getChunk(p) } }
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun getChunk(pos: UniversePos): UniverseChunk? {
|
||||||
return getChunk(world2chunk(Vector2i(pos.location)))
|
return getChunk(world2chunk(Vector2i(pos.location)))
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getChunk(pos: Vector2i): CompletableFuture<KOptional<UniverseChunk>> {
|
suspend fun getChunk(pos: Vector2i): UniverseChunk? {
|
||||||
return chunkCache.get(pos) { p -> chainOptionalFutures(sources) { it.getChunk(p) } }
|
val get = getChunkFuture(pos).await()
|
||||||
|
|
||||||
|
if (get.isPresent) {
|
||||||
|
return get.value
|
||||||
|
} else {
|
||||||
|
return null
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
init {
|
init {
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
package ru.dbotthepony.kstarbound.server.world
|
package ru.dbotthepony.kstarbound.server.world
|
||||||
|
|
||||||
|
import com.google.gson.JsonObject
|
||||||
import it.unimi.dsi.fastutil.longs.Long2ObjectFunction
|
import it.unimi.dsi.fastutil.longs.Long2ObjectFunction
|
||||||
import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap
|
import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap
|
||||||
import it.unimi.dsi.fastutil.objects.ObjectAVLTreeSet
|
import it.unimi.dsi.fastutil.objects.ObjectAVLTreeSet
|
||||||
@ -7,6 +8,8 @@ import ru.dbotthepony.kommons.collect.chainOptionalFutures
|
|||||||
import ru.dbotthepony.kommons.util.KOptional
|
import ru.dbotthepony.kommons.util.KOptional
|
||||||
import ru.dbotthepony.kstarbound.Starbound
|
import ru.dbotthepony.kstarbound.Starbound
|
||||||
import ru.dbotthepony.kstarbound.client.network.packets.JoinWorldPacket
|
import ru.dbotthepony.kstarbound.client.network.packets.JoinWorldPacket
|
||||||
|
import ru.dbotthepony.kstarbound.defs.world.WorldTemplate
|
||||||
|
import ru.dbotthepony.kstarbound.network.packets.clientbound.WorldStartPacket
|
||||||
import ru.dbotthepony.kstarbound.server.StarboundServer
|
import ru.dbotthepony.kstarbound.server.StarboundServer
|
||||||
import ru.dbotthepony.kstarbound.server.ServerConnection
|
import ru.dbotthepony.kstarbound.server.ServerConnection
|
||||||
import ru.dbotthepony.kstarbound.util.ExecutionSpinner
|
import ru.dbotthepony.kstarbound.util.ExecutionSpinner
|
||||||
@ -19,6 +22,7 @@ import ru.dbotthepony.kstarbound.world.entities.AbstractEntity
|
|||||||
import java.util.Collections
|
import java.util.Collections
|
||||||
import java.util.concurrent.CompletableFuture
|
import java.util.concurrent.CompletableFuture
|
||||||
import java.util.concurrent.RejectedExecutionException
|
import java.util.concurrent.RejectedExecutionException
|
||||||
|
import java.util.concurrent.atomic.AtomicBoolean
|
||||||
import java.util.concurrent.atomic.AtomicInteger
|
import java.util.concurrent.atomic.AtomicInteger
|
||||||
import java.util.concurrent.locks.LockSupport
|
import java.util.concurrent.locks.LockSupport
|
||||||
import java.util.concurrent.locks.ReentrantLock
|
import java.util.concurrent.locks.ReentrantLock
|
||||||
@ -28,9 +32,8 @@ import kotlin.concurrent.withLock
|
|||||||
|
|
||||||
class ServerWorld(
|
class ServerWorld(
|
||||||
val server: StarboundServer,
|
val server: StarboundServer,
|
||||||
seed: Long,
|
|
||||||
geometry: WorldGeometry,
|
geometry: WorldGeometry,
|
||||||
) : World<ServerWorld, ServerChunk>(seed, geometry) {
|
) : World<ServerWorld, ServerChunk>(geometry) {
|
||||||
init {
|
init {
|
||||||
server.worlds.add(this)
|
server.worlds.add(this)
|
||||||
}
|
}
|
||||||
@ -41,9 +44,29 @@ class ServerWorld(
|
|||||||
private fun doAcceptPlayer(player: ServerConnection): Boolean {
|
private fun doAcceptPlayer(player: ServerConnection): Boolean {
|
||||||
if (player !in internalPlayers) {
|
if (player !in internalPlayers) {
|
||||||
internalPlayers.add(player)
|
internalPlayers.add(player)
|
||||||
|
player.onLeaveWorld()
|
||||||
player.world?.removePlayer(player)
|
player.world?.removePlayer(player)
|
||||||
player.world = this
|
player.world = this
|
||||||
player.sendAndFlush(JoinWorldPacket(this))
|
|
||||||
|
if (player.isLegacy) {
|
||||||
|
player.sendAndFlush(WorldStartPacket(
|
||||||
|
templateData = WorldTemplate(geometry).toJson(true),
|
||||||
|
skyData = ByteArray(0),
|
||||||
|
weatherData = ByteArray(0),
|
||||||
|
playerStart = playerSpawnPosition,
|
||||||
|
playerRespawn = playerSpawnPosition,
|
||||||
|
respawnInWorld = respawnInWorld,
|
||||||
|
dungeonGravity = mapOf(),
|
||||||
|
dungeonBreathable = mapOf(),
|
||||||
|
protectedDungeonIDs = setOf(),
|
||||||
|
worldProperties = JsonObject(),
|
||||||
|
connectionID = player.connectionID,
|
||||||
|
localInterpolationMode = false,
|
||||||
|
))
|
||||||
|
} else {
|
||||||
|
player.sendAndFlush(JoinWorldPacket(this))
|
||||||
|
}
|
||||||
|
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -51,6 +74,9 @@ class ServerWorld(
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun acceptPlayer(player: ServerConnection): CompletableFuture<Boolean> {
|
fun acceptPlayer(player: ServerConnection): CompletableFuture<Boolean> {
|
||||||
|
check(!isClosed.get()) { "$this is invalid" }
|
||||||
|
unpause()
|
||||||
|
|
||||||
try {
|
try {
|
||||||
return CompletableFuture.supplyAsync(Supplier { doAcceptPlayer(player) }, mailbox)
|
return CompletableFuture.supplyAsync(Supplier { doAcceptPlayer(player) }, mailbox)
|
||||||
} catch (err: RejectedExecutionException) {
|
} catch (err: RejectedExecutionException) {
|
||||||
@ -71,12 +97,10 @@ class ServerWorld(
|
|||||||
}
|
}
|
||||||
|
|
||||||
val spinner = ExecutionSpinner(mailbox::executeQueuedTasks, ::spin, Starbound.TICK_TIME_ADVANCE_NANOS)
|
val spinner = ExecutionSpinner(mailbox::executeQueuedTasks, ::spin, Starbound.TICK_TIME_ADVANCE_NANOS)
|
||||||
val thread = Thread(spinner, "Starbound Server World $seed")
|
val thread = Thread(spinner, "Starbound Server World Thread")
|
||||||
val ticketListLock = ReentrantLock()
|
val ticketListLock = ReentrantLock()
|
||||||
|
|
||||||
@Volatile
|
private val isClosed = AtomicBoolean()
|
||||||
var isClosed: Boolean = false
|
|
||||||
private set
|
|
||||||
|
|
||||||
init {
|
init {
|
||||||
thread.isDaemon = true
|
thread.isDaemon = true
|
||||||
@ -89,10 +113,18 @@ class ServerWorld(
|
|||||||
chunkProviders.add(source)
|
chunkProviders.add(source)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun pause() {
|
||||||
|
if (!isClosed.get()) spinner.pause()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun unpause() {
|
||||||
|
if (!isClosed.get()) spinner.unpause()
|
||||||
|
}
|
||||||
|
|
||||||
override fun close() {
|
override fun close() {
|
||||||
if (!isClosed) {
|
if (isClosed.compareAndSet(false, true)) {
|
||||||
super.close()
|
super.close()
|
||||||
isClosed = true
|
spinner.unpause()
|
||||||
|
|
||||||
lock.withLock {
|
lock.withLock {
|
||||||
internalPlayers.forEach {
|
internalPlayers.forEach {
|
||||||
@ -105,7 +137,7 @@ class ServerWorld(
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun spin(): Boolean {
|
private fun spin(): Boolean {
|
||||||
if (isClosed) return false
|
if (isClosed.get()) return false
|
||||||
|
|
||||||
try {
|
try {
|
||||||
think()
|
think()
|
||||||
@ -124,7 +156,7 @@ class ServerWorld(
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun thinkInner() {
|
override fun thinkInner() {
|
||||||
internalPlayers.forEach { if (!isClosed) it.tick() }
|
internalPlayers.forEach { if (!isClosed.get()) it.tick() }
|
||||||
|
|
||||||
ticketListLock.withLock {
|
ticketListLock.withLock {
|
||||||
ticketLists.removeIf {
|
ticketLists.removeIf {
|
||||||
|
@ -18,11 +18,10 @@ import ru.dbotthepony.kommons.gson.getArray
|
|||||||
import ru.dbotthepony.kommons.util.KOptional
|
import ru.dbotthepony.kommons.util.KOptional
|
||||||
import ru.dbotthepony.kommons.vector.Vector2i
|
import ru.dbotthepony.kommons.vector.Vector2i
|
||||||
import ru.dbotthepony.kommons.vector.Vector3i
|
import ru.dbotthepony.kommons.vector.Vector3i
|
||||||
import ru.dbotthepony.kstarbound.defs.CelestialParameters
|
import ru.dbotthepony.kstarbound.defs.world.CelestialParameters
|
||||||
import ru.dbotthepony.kstarbound.defs.CelestialPlanet
|
import ru.dbotthepony.kstarbound.defs.world.CelestialPlanet
|
||||||
import ru.dbotthepony.kstarbound.json.pairAdapter
|
import ru.dbotthepony.kstarbound.json.pairAdapter
|
||||||
import ru.dbotthepony.kstarbound.json.pairListAdapter
|
import ru.dbotthepony.kstarbound.json.pairListAdapter
|
||||||
import ru.dbotthepony.kstarbound.json.pairSetAdapter
|
|
||||||
import ru.dbotthepony.kstarbound.world.UniversePos
|
import ru.dbotthepony.kstarbound.world.UniversePos
|
||||||
|
|
||||||
class UniverseChunk(var chunkPos: Vector2i = Vector2i.ZERO) {
|
class UniverseChunk(var chunkPos: Vector2i = Vector2i.ZERO) {
|
||||||
@ -31,15 +30,15 @@ class UniverseChunk(var chunkPos: Vector2i = Vector2i.ZERO) {
|
|||||||
val systems = Object2ObjectOpenHashMap<Vector3i, System>()
|
val systems = Object2ObjectOpenHashMap<Vector3i, System>()
|
||||||
val constellations = ObjectOpenHashSet<Pair<Vector2i, Vector2i>>()
|
val constellations = ObjectOpenHashSet<Pair<Vector2i, Vector2i>>()
|
||||||
|
|
||||||
fun parameters(coordinate: UniversePos): KOptional<CelestialParameters> {
|
fun parameters(coordinate: UniversePos): CelestialParameters? {
|
||||||
val system = systems[coordinate.location] ?: return KOptional()
|
val system = systems[coordinate.location] ?: return null
|
||||||
|
|
||||||
if (coordinate.isSystem)
|
if (coordinate.isSystem)
|
||||||
return KOptional(system.parameters)
|
return system.parameters
|
||||||
else if (coordinate.isPlanet)
|
else if (coordinate.isPlanet)
|
||||||
return KOptional.ofNullable(system.planets[coordinate.planetOrbit]?.parameters)
|
return system.planets[coordinate.planetOrbit]?.parameters
|
||||||
else if (coordinate.isSatellite)
|
else if (coordinate.isSatellite)
|
||||||
return KOptional.ofNullable(system.planets[coordinate.planetOrbit]?.satellites?.get(coordinate.satelliteOrbit))
|
return system.planets[coordinate.planetOrbit]?.satellites?.get(coordinate.satelliteOrbit)
|
||||||
else
|
else
|
||||||
throw RuntimeException("unreachable code")
|
throw RuntimeException("unreachable code")
|
||||||
}
|
}
|
||||||
|
@ -17,13 +17,14 @@ import ru.dbotthepony.kommons.util.KOptional
|
|||||||
import ru.dbotthepony.kommons.vector.Vector2i
|
import ru.dbotthepony.kommons.vector.Vector2i
|
||||||
import ru.dbotthepony.kommons.vector.Vector3i
|
import ru.dbotthepony.kommons.vector.Vector3i
|
||||||
import ru.dbotthepony.kstarbound.Starbound
|
import ru.dbotthepony.kstarbound.Starbound
|
||||||
import ru.dbotthepony.kstarbound.defs.CelestialParameters
|
import ru.dbotthepony.kstarbound.defs.world.CelestialParameters
|
||||||
import ru.dbotthepony.kstarbound.defs.CelestialPlanet
|
import ru.dbotthepony.kstarbound.defs.world.CelestialPlanet
|
||||||
import ru.dbotthepony.kstarbound.defs.JsonDriven
|
import ru.dbotthepony.kstarbound.defs.JsonDriven
|
||||||
import ru.dbotthepony.kstarbound.io.BTreeDB5
|
import ru.dbotthepony.kstarbound.io.BTreeDB5
|
||||||
import ru.dbotthepony.kstarbound.json.readJsonElement
|
import ru.dbotthepony.kstarbound.json.readJsonElement
|
||||||
import ru.dbotthepony.kstarbound.json.writeJsonElement
|
import ru.dbotthepony.kstarbound.json.writeJsonElement
|
||||||
import ru.dbotthepony.kstarbound.math.Line2d
|
import ru.dbotthepony.kstarbound.math.Line2d
|
||||||
|
import ru.dbotthepony.kstarbound.util.binnedChoice
|
||||||
import ru.dbotthepony.kstarbound.util.paddedNumber
|
import ru.dbotthepony.kstarbound.util.paddedNumber
|
||||||
import ru.dbotthepony.kstarbound.util.random.random
|
import ru.dbotthepony.kstarbound.util.random.random
|
||||||
import ru.dbotthepony.kstarbound.util.random.staticRandom64
|
import ru.dbotthepony.kstarbound.util.random.staticRandom64
|
||||||
@ -69,7 +70,7 @@ private fun key(chunkPos: IStruct2i): ByteKey {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class LegacyUniverseSource(private val db: BTreeDB5) : UniverseSource(), Closeable {
|
class LegacyUniverseSource(private val db: BTreeDB5) : UniverseSource(), Closeable {
|
||||||
private val carried = CarriedExecutor(Starbound.STORAGE_IO_POOL)
|
private val carried = CarriedExecutor(Starbound.IO_EXECUTOR)
|
||||||
|
|
||||||
override fun close() {
|
override fun close() {
|
||||||
carried.shutdown()
|
carried.shutdown()
|
||||||
@ -168,9 +169,7 @@ class NativeUniverseSource(private val db: BTreeDB6?, private val universe: Serv
|
|||||||
|
|
||||||
val type = universe.generationInformation.systemTypeBins
|
val type = universe.generationInformation.systemTypeBins
|
||||||
.stream()
|
.stream()
|
||||||
.sorted { o1, o2 -> o2.first.compareTo(o1.first) }
|
.binnedChoice(typeSelector).orElse("")
|
||||||
.filter { it.first <= typeSelector }
|
|
||||||
.findFirst().map { it.second }.orElse("")
|
|
||||||
|
|
||||||
if (type.isBlank())
|
if (type.isBlank())
|
||||||
return null
|
return null
|
||||||
@ -365,7 +364,7 @@ class NativeUniverseSource(private val db: BTreeDB6?, private val universe: Serv
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private val carrier = CarriedExecutor(Starbound.STORAGE_IO_POOL)
|
private val carrier = CarriedExecutor(Starbound.IO_EXECUTOR)
|
||||||
val reader: UniverseSource = Reader()
|
val reader: UniverseSource = Reader()
|
||||||
val generator: UniverseSource = Generator()
|
val generator: UniverseSource = Generator()
|
||||||
|
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
package ru.dbotthepony.kstarbound.util
|
package ru.dbotthepony.kstarbound.util
|
||||||
|
|
||||||
import ru.dbotthepony.kstarbound.Starbound
|
import ru.dbotthepony.kstarbound.Starbound
|
||||||
|
import java.io.File
|
||||||
|
|
||||||
object AssetPathStack {
|
object AssetPathStack {
|
||||||
private val _stack = object : ThreadLocal<ArrayDeque<String>>() {
|
private val _stack = object : ThreadLocal<ArrayDeque<String>>() {
|
||||||
@ -49,4 +50,11 @@ object AssetPathStack {
|
|||||||
fun remapSafe(path: String): String {
|
fun remapSafe(path: String): String {
|
||||||
return remap(last() ?: return path, path)
|
return remap(last() ?: return path, path)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun relativeTo(base: String, path: String): String {
|
||||||
|
if (path.isNotEmpty() && path[0] == '/')
|
||||||
|
return path
|
||||||
|
|
||||||
|
return if (base.endsWith('/')) "$base$path" else "${base.substringBeforeLast('/')}/$path"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user