293 lines
15 KiB
Kotlin
293 lines
15 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.JsonObject
|
|
import com.google.gson.JsonSyntaxException
|
|
import com.google.gson.TypeAdapterFactory
|
|
import com.google.gson.reflect.TypeToken
|
|
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.util.KOptional
|
|
import ru.dbotthepony.kstarbound.defs.AssetReference
|
|
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.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.npc.NpcTypeDefinition
|
|
import ru.dbotthepony.kstarbound.defs.npc.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.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.item.ItemRegistry
|
|
import ru.dbotthepony.kstarbound.json.JsonPatch
|
|
import ru.dbotthepony.kstarbound.json.builder.JsonFactory
|
|
import ru.dbotthepony.kstarbound.world.terrain.TerrainSelectorType
|
|
import ru.dbotthepony.kstarbound.util.AssetPathStack
|
|
import ru.dbotthepony.kstarbound.world.physics.CollisionType
|
|
import java.util.*
|
|
import java.util.concurrent.CompletableFuture
|
|
import java.util.concurrent.Future
|
|
import kotlin.collections.ArrayList
|
|
|
|
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 npcTypes = Registry<NpcTypeDefinition>("npc type").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 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()) }
|
|
|
|
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(Starbound.ELEMENTS_ADAPTER.read(listedFile.asyncJsonReader().await()), patches[listedFile.computeFullPath()])
|
|
|
|
AssetPathStack(listedFile.computeDirectory()) {
|
|
val read = adapter.fromJsonTree(elem)
|
|
val keys = keyProvider(read)
|
|
|
|
after(read, listedFile)
|
|
|
|
registry.add {
|
|
registry.add(
|
|
key = keys.first,
|
|
value = read,
|
|
id = keys.second,
|
|
json = elem,
|
|
file = listedFile
|
|
)
|
|
}
|
|
}
|
|
} catch (err: Throwable) {
|
|
LOGGER.error("Loading ${registry.name} definition file $listedFile", err)
|
|
}
|
|
}.asCompletableFuture()
|
|
}
|
|
}
|
|
|
|
fun finishLoad() {
|
|
registriesInternal.forEach { it.finishLoad() }
|
|
}
|
|
|
|
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(loadRegistry(worldObjects, patchTree, fileTree["object"] ?: listOf(), key(ObjectDefinition::objectName)))
|
|
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(npcTypes, patchTree, fileTree["npctype"] ?: listOf(), key(NpcTypeDefinition::type)))
|
|
tasks.addAll(loadRegistry(monsterSkills, patchTree, fileTree["monsterskill"] ?: listOf(), key(MonsterSkillDefinition::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(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 })
|
|
|
|
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(Starbound.ELEMENTS_ADAPTER.read(listedFile.asyncJsonReader().await()), patches[listedFile.computeFullPath()]) as JsonObject
|
|
|
|
for ((k, v) in json.entrySet()) {
|
|
try {
|
|
val value = adapter.fromJsonTree(v)
|
|
transform(value, k)
|
|
|
|
registry.add {
|
|
registry.add(k, value, v, listedFile)
|
|
}
|
|
} 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 suspend fun loadTerrainSelector(listedFile: IStarboundFile, type: TerrainSelectorType?, patches: Map<String, Collection<IStarboundFile>>) {
|
|
try {
|
|
val json = JsonPatch.applyAsync(Starbound.ELEMENTS_ADAPTER.read(listedFile.asyncJsonReader().await()), patches[listedFile.computeFullPath()]) as JsonObject
|
|
val name = json["name"]?.asString ?: throw JsonSyntaxException("Missing 'name' field")
|
|
val factory = TerrainSelectorType.factory(json, false, type)
|
|
|
|
terrainSelectors.add {
|
|
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>>() {}).fromJsonTree(read)
|
|
|
|
for (def in read2) {
|
|
tiles.add {
|
|
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()
|
|
}
|
|
}
|