473 lines
23 KiB
Kotlin
473 lines
23 KiB
Kotlin
package ru.dbotthepony.kstarbound
|
|
|
|
import com.google.common.collect.ImmutableList
|
|
import com.google.common.collect.ImmutableMap
|
|
import com.google.gson.GsonBuilder
|
|
import com.google.gson.JsonArray
|
|
import com.google.gson.JsonElement
|
|
import com.google.gson.JsonNull
|
|
import com.google.gson.JsonObject
|
|
import com.google.gson.JsonPrimitive
|
|
import com.google.gson.JsonSyntaxException
|
|
import com.google.gson.TypeAdapterFactory
|
|
import com.google.gson.reflect.TypeToken
|
|
import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet
|
|
import kotlinx.coroutines.async
|
|
import kotlinx.coroutines.future.asCompletableFuture
|
|
import kotlinx.coroutines.future.await
|
|
import kotlinx.coroutines.launch
|
|
import org.apache.logging.log4j.LogManager
|
|
import ru.dbotthepony.kommons.gson.contains
|
|
import ru.dbotthepony.kommons.util.KOptional
|
|
import ru.dbotthepony.kstarbound.defs.AssetReference
|
|
import ru.dbotthepony.kstarbound.defs.DamageKind
|
|
import ru.dbotthepony.kstarbound.defs.Json2Function
|
|
import ru.dbotthepony.kstarbound.defs.JsonConfigFunction
|
|
import ru.dbotthepony.kstarbound.defs.JsonFunction
|
|
import ru.dbotthepony.kstarbound.defs.MarkovTextGenerator
|
|
import ru.dbotthepony.kstarbound.defs.actor.Species
|
|
import ru.dbotthepony.kstarbound.defs.StatusEffectDefinition
|
|
import ru.dbotthepony.kstarbound.defs.ThingDescription
|
|
import ru.dbotthepony.kstarbound.defs.item.TreasurePoolDefinition
|
|
import ru.dbotthepony.kstarbound.defs.monster.MonsterSkillDefinition
|
|
import ru.dbotthepony.kstarbound.defs.monster.MonsterTypeDefinition
|
|
import ru.dbotthepony.kstarbound.defs.actor.TenantDefinition
|
|
import ru.dbotthepony.kstarbound.defs.`object`.ObjectDefinition
|
|
import ru.dbotthepony.kstarbound.defs.actor.player.TechDefinition
|
|
import ru.dbotthepony.kstarbound.defs.animation.ParticleConfig
|
|
import ru.dbotthepony.kstarbound.defs.dungeon.DungeonDefinition
|
|
import ru.dbotthepony.kstarbound.defs.item.TreasureChestDefinition
|
|
import ru.dbotthepony.kstarbound.defs.ProjectileDefinition
|
|
import ru.dbotthepony.kstarbound.defs.actor.DanceDefinition
|
|
import ru.dbotthepony.kstarbound.defs.actor.behavior.BehaviorDefinition
|
|
import ru.dbotthepony.kstarbound.defs.actor.behavior.BehaviorNodeDefinition
|
|
import ru.dbotthepony.kstarbound.defs.monster.MonsterPaletteSwap
|
|
import ru.dbotthepony.kstarbound.defs.monster.MonsterPartDefinition
|
|
import ru.dbotthepony.kstarbound.defs.quest.QuestTemplate
|
|
import ru.dbotthepony.kstarbound.defs.tile.LiquidDefinition
|
|
import ru.dbotthepony.kstarbound.defs.tile.RenderParameters
|
|
import ru.dbotthepony.kstarbound.defs.tile.TileDamageParameters
|
|
import ru.dbotthepony.kstarbound.defs.tile.TileModifierDefinition
|
|
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.BiomeDefinition
|
|
import ru.dbotthepony.kstarbound.defs.world.SpawnType
|
|
import ru.dbotthepony.kstarbound.item.ItemRegistry
|
|
import ru.dbotthepony.kstarbound.json.JsonPatch
|
|
import ru.dbotthepony.kstarbound.json.builder.JsonFactory
|
|
import ru.dbotthepony.kstarbound.json.fromJsonTreeFast
|
|
import ru.dbotthepony.kstarbound.json.mergeJson
|
|
import ru.dbotthepony.kstarbound.world.terrain.TerrainSelectorType
|
|
import ru.dbotthepony.kstarbound.util.AssetPathStack
|
|
import ru.dbotthepony.kstarbound.util.random.random
|
|
import ru.dbotthepony.kstarbound.world.physics.CollisionType
|
|
import java.util.*
|
|
import java.util.concurrent.CompletableFuture
|
|
import java.util.concurrent.Future
|
|
import java.util.random.RandomGenerator
|
|
import kotlin.NoSuchElementException
|
|
import kotlin.collections.ArrayList
|
|
import kotlin.collections.HashMap
|
|
|
|
object Registries {
|
|
private val LOGGER = LogManager.getLogger()
|
|
|
|
private val registriesInternal = ArrayList<Registry<*>>()
|
|
val registries: List<Registry<*>> = Collections.unmodifiableList(registriesInternal)
|
|
private val adapters = ArrayList<TypeAdapterFactory>()
|
|
|
|
fun registerAdapters(gsonBuilder: GsonBuilder) {
|
|
adapters.forEach { gsonBuilder.registerTypeAdapterFactory(it) }
|
|
}
|
|
|
|
val tiles = Registry<TileDefinition>("tiles").also(registriesInternal::add).also { adapters.add(it.adapter()) }
|
|
val tileModifiers = Registry<TileModifierDefinition>("tile modifiers").also(registriesInternal::add).also { adapters.add(it.adapter()) }
|
|
val liquid = Registry<LiquidDefinition>("liquid").also(registriesInternal::add).also { adapters.add(it.adapter()) }
|
|
val species = Registry<Species>("species").also(registriesInternal::add).also { adapters.add(it.adapter()) }
|
|
val statusEffects = Registry<StatusEffectDefinition>("status effect").also(registriesInternal::add).also { adapters.add(it.adapter()) }
|
|
val particles = Registry<ParticleConfig>("particle").also(registriesInternal::add).also { adapters.add(it.adapter()) }
|
|
val questTemplates = Registry<QuestTemplate>("quest template").also(registriesInternal::add).also { adapters.add(it.adapter()) }
|
|
val techs = Registry<TechDefinition>("tech").also(registriesInternal::add).also { adapters.add(it.adapter()) }
|
|
val jsonFunctions = Registry<JsonFunction>("json function").also(registriesInternal::add).also { adapters.add(it.adapter()) }
|
|
val json2Functions = Registry<Json2Function>("json 2function").also(registriesInternal::add).also { adapters.add(it.adapter()) }
|
|
val jsonConfigFunctions = Registry<JsonConfigFunction>("json config function").also(registriesInternal::add).also { adapters.add(it.adapter()) }
|
|
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 treasureChests = Registry<TreasureChestDefinition>("treasure chest").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 spawnTypes = Registry<SpawnType>("spawn type").also(registriesInternal::add).also { adapters.add(it.adapter()) }
|
|
val monsterPalettes = Registry<MonsterPaletteSwap>("monster palette").also(registriesInternal::add).also { adapters.add(it.adapter()) }
|
|
val behavior = Registry<BehaviorDefinition>("behavior").also(registriesInternal::add).also { adapters.add(it.adapter()) }
|
|
val behaviorNodes = Registry<BehaviorNodeDefinition>("behavior node").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<TerrainSelectorType.Factory<*, *>>("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()) }
|
|
val dungeons = Registry<DungeonDefinition>("dungeon").also(registriesInternal::add).also { adapters.add(it.adapter()) }
|
|
val markovGenerators = Registry<MarkovTextGenerator>("markov text generator").also(registriesInternal::add).also { adapters.add(it.adapter()) }
|
|
val damageKinds = Registry<DamageKind>("damage kind").also(registriesInternal::add).also { adapters.add(it.adapter()) }
|
|
val dance = Registry<DanceDefinition>("dance").also(registriesInternal::add).also { adapters.add(it.adapter()) }
|
|
|
|
private val monsterParts = HashMap<Pair<String, String>, HashMap<String, Pair<MonsterPartDefinition, IStarboundFile>>>()
|
|
private val loggedMonsterPartMisses = Collections.synchronizedSet(ObjectOpenHashSet<Pair<String, String>>())
|
|
private val stagehands = HashMap<String, Pair<JsonObject, IStarboundFile>>()
|
|
|
|
fun makeStagehandConfig(type: String, overrides: JsonElement? = JsonNull.INSTANCE): JsonObject {
|
|
val (data) = stagehands[type] ?: throw NoSuchElementException("No such stagehand: $type")
|
|
return mergeJson(data.deepCopy(), overrides ?: JsonNull.INSTANCE)
|
|
}
|
|
|
|
private fun loadStagehands(files: Collection<IStarboundFile>, patches: Map<String, Collection<IStarboundFile>>): List<Future<*>> {
|
|
return files.map { listedFile ->
|
|
Starbound.GLOBAL_SCOPE.launch {
|
|
try {
|
|
val elem = JsonPatch.applyAsync(listedFile.asyncJsonReader(), patches[listedFile.computeFullPath()]).asJsonObject
|
|
val type = elem["type"].asString
|
|
|
|
Starbound.submit {
|
|
val existing = stagehands.put(type, elem to listedFile)
|
|
|
|
if (existing != null) {
|
|
LOGGER.warn("Redefining stagehand prototype $type (new def originate from $listedFile, existing originate from ${existing.second})")
|
|
}
|
|
}
|
|
} catch (err: Throwable) {
|
|
LOGGER.error("Loading stagehand definition file $listedFile", err)
|
|
}
|
|
}.asCompletableFuture()
|
|
}
|
|
}
|
|
|
|
fun selectMonsterPart(category: String, type: String, random: RandomGenerator): MonsterPartDefinition? {
|
|
val key = category to type
|
|
val get = monsterParts[key]
|
|
|
|
if (get.isNullOrEmpty()) {
|
|
if (loggedMonsterPartMisses.add(key)) {
|
|
LOGGER.error("No such monster part combination of category '$category' and type '$type'")
|
|
}
|
|
|
|
return null
|
|
}
|
|
|
|
return get.values.random(random, true).first
|
|
}
|
|
|
|
fun getMonsterPart(category: String, type: String, name: String): MonsterPartDefinition? {
|
|
return monsterParts[category to type]?.get(name)?.first
|
|
}
|
|
|
|
private fun loadMonsterParts(files: Collection<IStarboundFile>, patches: Map<String, Collection<IStarboundFile>>): List<Future<*>> {
|
|
val adapter by lazy { Starbound.gson.getAdapter(MonsterPartDefinition::class.java) }
|
|
|
|
return files.map { listedFile ->
|
|
Starbound.GLOBAL_SCOPE.launch {
|
|
try {
|
|
val elem = JsonPatch.applyAsync(listedFile.asyncJsonReader(), patches[listedFile.computeFullPath()])
|
|
val next = AssetPathStack(listedFile.computeFullPath()) { adapter.fromJsonTreeFast(elem) }
|
|
|
|
Starbound.submit {
|
|
val map = monsterParts.computeIfAbsent(next.category to next.type) { HashMap() }
|
|
val old = map[next.name]
|
|
|
|
if (old != null) {
|
|
LOGGER.error("Duplicate monster part '${next.name}' of category '${next.category}' and type '${next.type}', originating from $listedFile (old originate from ${old.second})")
|
|
} else {
|
|
map[next.name] = next to listedFile
|
|
}
|
|
}
|
|
} catch (err: Throwable) {
|
|
LOGGER.error("Loading monster part definition file $listedFile", err)
|
|
}
|
|
}.asCompletableFuture()
|
|
}
|
|
}
|
|
|
|
private val npcTypes = HashMap<String, Pair<IStarboundFile, JsonObject>>()
|
|
|
|
fun buildNPCConfig(type: String, overrides: JsonElement = JsonNull.INSTANCE): JsonObject {
|
|
val baseConfig = npcTypes.getOrElse(type) { throw NoSuchElementException("No such NPC type $type") }.second
|
|
val config = mergeJson(baseConfig.deepCopy(), overrides)
|
|
|
|
if ("baseType" in baseConfig) {
|
|
return buildNPCConfig(baseConfig["baseType"].asString, config)
|
|
} else {
|
|
return config
|
|
}
|
|
}
|
|
|
|
private fun loadNpcTypes(files: Collection<IStarboundFile>, patches: Map<String, Collection<IStarboundFile>>): List<Future<*>> {
|
|
return files.map { listedFile ->
|
|
Starbound.GLOBAL_SCOPE.launch {
|
|
try {
|
|
val elem = JsonPatch.applyAsync(listedFile.asyncJsonReader(), patches[listedFile.computeFullPath()]) as JsonObject
|
|
val type = elem.get("type").asString
|
|
|
|
if ("scripts" in elem) {
|
|
val fPath = listedFile.computeFullPath()
|
|
val scripts = elem["scripts"].asJsonArray
|
|
|
|
for (i in 0 until scripts.size()) {
|
|
scripts[i] = JsonPrimitive(AssetPathStack.relativeTo(fPath, scripts[i].asString))
|
|
}
|
|
}
|
|
|
|
Starbound.submit {
|
|
val existing = npcTypes.put(type, listedFile to elem)
|
|
|
|
if (existing != null) {
|
|
LOGGER.warn("Overwriting existing NPC definition '$type' (new originate from $listedFile, old originating from ${existing.first})")
|
|
}
|
|
}
|
|
} catch (err: Throwable) {
|
|
LOGGER.error("Loading NPC type definition file $listedFile", err)
|
|
}
|
|
}.asCompletableFuture()
|
|
}
|
|
}
|
|
|
|
private fun <T> key(mapper: (T) -> String): (T) -> Pair<String, KOptional<Int?>> {
|
|
return { mapper.invoke(it) to KOptional() }
|
|
}
|
|
|
|
private fun <T> key(mapper: (T) -> String, mapperInt: (T) -> Int?): (T) -> Pair<String, KOptional<Int?>> {
|
|
return { mapper.invoke(it) to KOptional(mapperInt.invoke(it)) }
|
|
}
|
|
|
|
fun validate(): CompletableFuture<Boolean> {
|
|
val futures = ArrayList<CompletableFuture<Boolean>>()
|
|
|
|
for (registry in registriesInternal)
|
|
futures.add(CompletableFuture.supplyAsync({ registry.validate() }, Starbound.EXECUTOR))
|
|
|
|
return CompletableFuture.allOf(*futures.toTypedArray()).thenApply { futures.all { it.get() } }
|
|
}
|
|
|
|
private inline fun <reified T : Any> loadRegistry(
|
|
registry: Registry<T>,
|
|
patches: Map<String, Collection<IStarboundFile>>,
|
|
files: Collection<IStarboundFile>,
|
|
noinline keyProvider: (T) -> Pair<String, KOptional<Int?>>,
|
|
noinline after: (T, IStarboundFile) -> Unit = { _, _ -> }
|
|
): List<Future<*>> {
|
|
val adapter by lazy { Starbound.gson.getAdapter(T::class.java) }
|
|
|
|
return files.map { listedFile ->
|
|
Starbound.GLOBAL_SCOPE.launch {
|
|
try {
|
|
val elem = JsonPatch.applyAsync(listedFile.asyncJsonReader(), patches[listedFile.computeFullPath()])
|
|
|
|
AssetPathStack(listedFile.computeFullPath()) {
|
|
val read = adapter.fromJsonTreeFast(elem)
|
|
val keys = keyProvider(read)
|
|
|
|
after(read, listedFile)
|
|
|
|
Starbound.submit {
|
|
registry.add(
|
|
key = keys.first,
|
|
value = read,
|
|
id = keys.second,
|
|
json = elem,
|
|
file = listedFile
|
|
)
|
|
}.exceptionally { err -> LOGGER.error("Loading ${registry.name} definition file $listedFile", err); null }
|
|
}
|
|
} catch (err: Throwable) {
|
|
LOGGER.error("Loading ${registry.name} definition file $listedFile", err)
|
|
}
|
|
}.asCompletableFuture()
|
|
}
|
|
}
|
|
|
|
fun load(fileTree: Map<String, Collection<IStarboundFile>>, patchTree: Map<String, List<IStarboundFile>>): List<Future<*>> {
|
|
val tasks = ArrayList<Future<*>>()
|
|
|
|
tasks.addAll(ItemRegistry.load(fileTree, patchTree))
|
|
|
|
tasks.addAll(loadTerrainSelectors(fileTree, patchTree))
|
|
|
|
tasks.addAll(loadRegistry(tiles, patchTree, fileTree["material"] ?: listOf(), key(TileDefinition::materialName, TileDefinition::materialId)))
|
|
tasks.addAll(loadRegistry(tileModifiers, patchTree, fileTree["matmod"] ?: listOf(), key(TileModifierDefinition::modName, TileModifierDefinition::modId)))
|
|
tasks.addAll(loadRegistry(liquid, patchTree, fileTree["liquid"] ?: listOf(), key(LiquidDefinition::name, LiquidDefinition::liquidId)))
|
|
|
|
tasks.add(loadMetaMaterials())
|
|
tasks.addAll(loadRegistry(dungeons, patchTree, fileTree["dungeon"] ?: listOf(), key(DungeonDefinition::name)))
|
|
|
|
tasks.addAll(loadMonsterParts(fileTree["monsterpart"] ?: listOf(), patchTree))
|
|
tasks.addAll(loadStagehands(fileTree["stagehand"] ?: listOf(), patchTree))
|
|
|
|
tasks.addAll(loadRegistry(worldObjects, patchTree, fileTree["object"] ?: listOf(), key(ObjectDefinition::objectName)))
|
|
tasks.addAll(loadRegistry(monsterTypes, patchTree, fileTree["monstertype"] ?: listOf(), key(MonsterTypeDefinition::type)))
|
|
tasks.addAll(loadRegistry(monsterPalettes, patchTree, fileTree["monstercolors"] ?: listOf(), key(MonsterPaletteSwap::name)))
|
|
tasks.addAll(loadRegistry(monsterSkills, patchTree, fileTree["monsterskill"] ?: listOf(), key(MonsterSkillDefinition::name)))
|
|
tasks.addAll(loadRegistry(statusEffects, patchTree, fileTree["statuseffect"] ?: listOf(), key(StatusEffectDefinition::name)))
|
|
tasks.addAll(loadRegistry(species, patchTree, fileTree["species"] ?: listOf(), key(Species::kind)))
|
|
tasks.addAll(loadRegistry(particles, patchTree, fileTree["particle"] ?: listOf(), { (it.kind ?: throw NullPointerException("Missing 'kind' value")) to KOptional() }))
|
|
tasks.addAll(loadRegistry(questTemplates, patchTree, fileTree["questtemplate"] ?: listOf(), key(QuestTemplate::id)))
|
|
tasks.addAll(loadRegistry(techs, patchTree, fileTree["tech"] ?: listOf(), key(TechDefinition::name)))
|
|
tasks.addAll(loadRegistry(biomes, patchTree, fileTree["biome"] ?: listOf(), key(BiomeDefinition::name)))
|
|
tasks.addAll(loadRegistry(grassVariants, patchTree, fileTree["grass"] ?: listOf(), key(GrassVariant.Data::name)))
|
|
tasks.addAll(loadRegistry(treeStemVariants, patchTree, fileTree["modularstem"] ?: listOf(), key(TreeVariant.StemData::name)))
|
|
tasks.addAll(loadRegistry(treeFoliageVariants, patchTree, fileTree["modularfoliage"] ?: listOf(), key(TreeVariant.FoliageData::name)))
|
|
tasks.addAll(loadRegistry(bushVariants, patchTree, fileTree["bush"] ?: listOf(), key(BushVariant.Data::name)))
|
|
tasks.addAll(loadRegistry(markovGenerators, patchTree, fileTree["namesource"] ?: listOf(), key(MarkovTextGenerator::name)))
|
|
tasks.addAll(loadRegistry(projectiles, patchTree, fileTree["projectile"] ?: listOf(), key(ProjectileDefinition::projectileName)))
|
|
tasks.addAll(loadRegistry(behavior, patchTree, fileTree["behavior"] ?: listOf(), key(BehaviorDefinition::name)))
|
|
tasks.addAll(loadRegistry(damageKinds, patchTree, fileTree["damage"] ?: listOf(), key(DamageKind::kind)))
|
|
tasks.addAll(loadRegistry(dance, patchTree, fileTree["dance"] ?: listOf(), key(DanceDefinition::name)))
|
|
|
|
tasks.addAll(loadCombined(behaviorNodes, fileTree["nodes"] ?: listOf(), patchTree))
|
|
|
|
tasks.addAll(loadCombined(jsonFunctions, fileTree["functions"] ?: listOf(), patchTree))
|
|
tasks.addAll(loadCombined(json2Functions, fileTree["2functions"] ?: listOf(), patchTree))
|
|
tasks.addAll(loadCombined(jsonConfigFunctions, fileTree["configfunctions"] ?: listOf(), patchTree))
|
|
tasks.addAll(loadCombined(treasureChests, fileTree["treasurechests"] ?: listOf(), patchTree) { name = it })
|
|
tasks.addAll(loadCombined(treasurePools, fileTree["treasurepools"] ?: listOf(), patchTree) { name = it })
|
|
|
|
// because someone couldn't handle their mushroom vine that day, and decided to make third way of
|
|
// declaring game data
|
|
tasks.addAll(loadMixed(spawnTypes, fileTree["spawntypes"] ?: listOf(), patchTree, SpawnType::name))
|
|
|
|
tasks.addAll(loadNpcTypes(fileTree["npctype"] ?: listOf(), patchTree))
|
|
|
|
return tasks
|
|
}
|
|
|
|
private inline fun <reified T : Any> loadCombined(registry: Registry<T>, files: Collection<IStarboundFile>, patches: Map<String, Collection<IStarboundFile>>, noinline transform: T.(String) -> Unit = {}): List<Future<*>> {
|
|
val adapter by lazy { Starbound.gson.getAdapter(T::class.java) }
|
|
|
|
return files.map { listedFile ->
|
|
Starbound.GLOBAL_SCOPE.launch {
|
|
try {
|
|
val json = JsonPatch.applyAsync(listedFile.asyncJsonReader(), patches[listedFile.computeFullPath()]) as JsonObject
|
|
|
|
for ((k, v) in json.entrySet()) {
|
|
try {
|
|
val value = adapter.fromJsonTreeFast(v)
|
|
transform(value, k)
|
|
|
|
Starbound.submit {
|
|
registry.add(k, value, v, listedFile)
|
|
}.exceptionally { err -> LOGGER.error("Loading ${registry.name} definition $k from file $listedFile", err); null }
|
|
} catch (err: Exception) {
|
|
LOGGER.error("Loading ${registry.name} definition $k from file $listedFile", err)
|
|
}
|
|
}
|
|
} catch (err: Exception) {
|
|
LOGGER.error("Loading ${registry.name} definition $listedFile", err)
|
|
}
|
|
}.asCompletableFuture()
|
|
}
|
|
}
|
|
|
|
private inline fun <reified T : Any> loadMixed(registry: Registry<T>, files: Collection<IStarboundFile>, patches: Map<String, Collection<IStarboundFile>>, noinline key: T.() -> String): List<Future<*>> {
|
|
val adapter by lazy { Starbound.gson.getAdapter(T::class.java) }
|
|
|
|
return files.map { listedFile ->
|
|
Starbound.GLOBAL_SCOPE.launch {
|
|
try {
|
|
val json = JsonPatch.applyAsync(listedFile.asyncJsonReader(), patches[listedFile.computeFullPath()]) as JsonArray
|
|
|
|
for ((i, v) in json.withIndex()) {
|
|
try {
|
|
val value = adapter.fromJsonTreeFast(v)
|
|
val getKey = key(value)
|
|
|
|
Starbound.submit {
|
|
registry.add(getKey, value, v, listedFile)
|
|
}.exceptionally { err -> LOGGER.error("Loading ${registry.name} definition at name '$getKey' from file $listedFile", err); null }
|
|
} catch (err: Exception) {
|
|
LOGGER.error("Loading ${registry.name} definition at index $i from file $listedFile", err)
|
|
}
|
|
}
|
|
} catch (err: Exception) {
|
|
LOGGER.error("Loading ${registry.name} definition $listedFile", err)
|
|
}
|
|
}.asCompletableFuture()
|
|
}
|
|
}
|
|
|
|
private suspend fun loadTerrainSelector(listedFile: IStarboundFile, type: TerrainSelectorType?, patches: Map<String, Collection<IStarboundFile>>) {
|
|
try {
|
|
val json = JsonPatch.applyAsync(listedFile.asyncJsonReader(), patches[listedFile.computeFullPath()]) as JsonObject
|
|
val name = json["name"]?.asString ?: throw JsonSyntaxException("Missing 'name' field")
|
|
val factory = TerrainSelectorType.factory(json, false, type)
|
|
|
|
Starbound.submit {
|
|
terrainSelectors.add(name, factory)
|
|
}
|
|
} catch (err: Exception) {
|
|
LOGGER.error("Loading terrain selector $listedFile", err)
|
|
}
|
|
}
|
|
|
|
private fun loadTerrainSelectors(files: Map<String, Collection<IStarboundFile>>, patches: Map<String, Collection<IStarboundFile>>): List<Future<*>> {
|
|
val tasks = ArrayList<Future<*>>()
|
|
|
|
tasks.addAll((files["terrain"] ?: listOf()).map { listedFile ->
|
|
Starbound.GLOBAL_SCOPE.async {
|
|
loadTerrainSelector(listedFile, null, patches)
|
|
}.asCompletableFuture()
|
|
})
|
|
|
|
// legacy files
|
|
for (type in TerrainSelectorType.entries) {
|
|
tasks.addAll((files[type.jsonName.lowercase()] ?: listOf()).map { listedFile ->
|
|
Starbound.GLOBAL_SCOPE.async {
|
|
loadTerrainSelector(listedFile, type, patches)
|
|
}.asCompletableFuture()
|
|
})
|
|
}
|
|
|
|
return tasks
|
|
}
|
|
|
|
@JsonFactory
|
|
data class MetaMaterialDef(
|
|
val materialId: Int,
|
|
val name: String,
|
|
val collisionKind: CollisionType,
|
|
val blocksLiquidFlow: Boolean = collisionKind.isSolidCollision,
|
|
val isConnectable: Boolean = true,
|
|
val supportsMods: Boolean = false,
|
|
)
|
|
|
|
private fun loadMetaMaterials(): Future<*> {
|
|
return Starbound.GLOBAL_SCOPE.async {
|
|
val read = Starbound.loadJsonAsset("/metamaterials.config").await() ?: return@async
|
|
val read2 = Starbound.gson.getAdapter(object : TypeToken<ImmutableList<MetaMaterialDef>>() {}).fromJsonTreeFast(read)
|
|
|
|
for (def in read2) {
|
|
Starbound.submit {
|
|
tiles.add(key = "metamaterial:${def.name}", id = KOptional(def.materialId), value = TileDefinition(
|
|
isMeta = true,
|
|
materialId = def.materialId,
|
|
materialName = "metamaterial:${def.name}",
|
|
descriptionData = ThingDescription.EMPTY,
|
|
category = "meta",
|
|
renderTemplate = AssetReference.empty(),
|
|
renderParameters = RenderParameters.META,
|
|
isConnectable = def.isConnectable,
|
|
supportsMods = def.supportsMods,
|
|
damageTable = AssetReference(TileDamageParameters(
|
|
damageFactors = ImmutableMap.of(),
|
|
damageRecovery = Double.MAX_VALUE,
|
|
maximumEffectTime = 0.0,
|
|
totalHealth = Double.MAX_VALUE,
|
|
harvestLevel = Int.MAX_VALUE,
|
|
))
|
|
))
|
|
}
|
|
}
|
|
}.asCompletableFuture()
|
|
}
|
|
}
|