diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/RecipeRegistry.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/RecipeRegistry.kt new file mode 100644 index 00000000..4e048442 --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/RecipeRegistry.kt @@ -0,0 +1,47 @@ +package ru.dbotthepony.kstarbound + +import it.unimi.dsi.fastutil.objects.Object2ObjectFunction +import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap +import ru.dbotthepony.kstarbound.defs.player.RecipeDefinition +import java.util.Collections + +class RecipeRegistry { + private val recipesInternal = ArrayList() + private val group2recipesInternal = Object2ObjectOpenHashMap>() + private val group2recipesBacking = Object2ObjectOpenHashMap>() + private val output2recipesInternal = Object2ObjectOpenHashMap>() + private val output2recipesBacking = Object2ObjectOpenHashMap>() + private val input2recipesInternal = Object2ObjectOpenHashMap>() + private val input2recipesBacking = Object2ObjectOpenHashMap>() + + val recipes: List = Collections.unmodifiableList(recipesInternal) + val group2recipes: Map> = Collections.unmodifiableMap(group2recipesBacking) + val output2recipes: Map> = Collections.unmodifiableMap(output2recipesBacking) + val input2recipes: Map> = Collections.unmodifiableMap(input2recipesBacking) + + fun add(recipe: RecipeDefinition) { + recipesInternal.add(recipe) + + for (group in recipe.groups) { + group2recipesInternal.computeIfAbsent(group, Object2ObjectFunction { p -> + ArrayList().also { + group2recipesBacking[p as String] = Collections.unmodifiableList(it) + } + }).add(recipe) + } + + output2recipesInternal.computeIfAbsent(recipe.output.item.name, Object2ObjectFunction { p -> + ArrayList().also { + output2recipesBacking[p as String] = Collections.unmodifiableList(it) + } + }).add(recipe) + + for (input in recipe.input) { + input2recipesInternal.computeIfAbsent(input.item.name, Object2ObjectFunction { p -> + ArrayList().also { + input2recipesBacking[p as String] = Collections.unmodifiableList(it) + } + }).add(recipe) + } + } +} diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/Starbound.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/Starbound.kt index 2e26f922..ec132c5a 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/Starbound.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/Starbound.kt @@ -35,7 +35,9 @@ import ru.dbotthepony.kstarbound.defs.tile.LiquidDefinition import ru.dbotthepony.kstarbound.defs.particle.ParticleDefinition import ru.dbotthepony.kstarbound.defs.player.BlueprintLearnList import ru.dbotthepony.kstarbound.defs.player.PlayerDefinition +import ru.dbotthepony.kstarbound.defs.player.RecipeDefinition import ru.dbotthepony.kstarbound.defs.player.TechDefinition +import ru.dbotthepony.kstarbound.defs.projectile.ProjectileDefinition import ru.dbotthepony.kstarbound.defs.quest.QuestTemplate import ru.dbotthepony.kstarbound.defs.tile.MaterialModifier import ru.dbotthepony.kstarbound.defs.tile.TileDefinition @@ -120,9 +122,14 @@ class Starbound : ISBFileLocator { private val _json2Functions = ObjectRegistry("json 2functions") val json2Functions = _json2Functions.view - private val _npcTypes = ObjectRegistry("npc types") + private val _npcTypes = ObjectRegistry("npc types", NpcTypeDefinition::type) val npcTypes = _npcTypes.view + private val _projectiles = ObjectRegistry("projectiles", ProjectileDefinition::projectileName) + val projectiles = _projectiles.view + + val recipeRegistry = RecipeRegistry() + val gson: Gson = with(GsonBuilder()) { serializeNulls() setDateFormat(DateFormat.LONG) @@ -197,6 +204,8 @@ class Starbound : ISBFileLocator { registerTypeAdapter(ItemDescriptor.Adapter(this@Starbound)) + registerTypeAdapterFactory(ItemReference.Factory(STRINGS)) + registerTypeAdapterFactory(with(RegistryReferenceFactory()) { add(tiles::get) add(tileModifiers::get) @@ -410,6 +419,28 @@ class Starbound : ISBFileLocator { TODO() } + state.setTableFunction("projectileGravityMultiplier", this) { args -> + // float root.projectileGravityMultiplier(String projectileName) + TODO() + } + + state.setTableFunction("projectileConfig", this) { args -> + // Json root.projectileConfig(String projectileName) + val name = args.getString() + args.lua.push(projectiles[name]?.copy() ?: throw kotlin.NoSuchElementException("No such Projectile type $name")) + 1 + } + + state.setTableFunction("recipesForItem", this) { args -> + args.lua.push(JsonArray().also { a -> + recipeRegistry.output2recipes[args.getString()]?.stream()?.map { gson.toJsonTree(it) }?.forEach { + a.add(it) + } + }) + + 1 + } + state.pop() state.load(polyfill, "@starbound.jar!/scripts/polyfill.lua") @@ -606,6 +637,7 @@ class Starbound : ISBFileLocator { loadStage(callback, { loadItemDefinitions(it, ext2files) }, "item definitions") loadStage(callback, { loadJsonFunctions(it, ext2files["functions"] ?: listOf()) }, "json functions") loadStage(callback, { loadJson2Functions(it, ext2files["2functions"] ?: listOf()) }, "json 2functions") + loadStage(callback, { loadRecipes(it, ext2files["recipe"] ?: listOf()) }, "recipes") loadStage(callback, _tiles, ext2files["material"] ?: listOf()) loadStage(callback, _tileModifiers, ext2files["matmod"] ?: listOf()) @@ -616,6 +648,7 @@ class Starbound : ISBFileLocator { loadStage(callback, _questTemplates, ext2files["questtemplate"] ?: listOf()) loadStage(callback, _techs, ext2files["tech"] ?: listOf()) loadStage(callback, _npcTypes, ext2files["npctype"] ?: listOf()) + loadStage(callback, _projectiles, ext2files["projectile"] ?: listOf()) pathStack.block("/") { //playerDefinition = gson.fromJson(locate("/player.config").reader(), PlayerDefinition::class.java) @@ -731,6 +764,21 @@ class Starbound : ISBFileLocator { } } + private fun loadRecipes(callback: (String) -> Unit, files: Collection) { + for (listedFile in files) { + try { + callback("Loading $listedFile") + recipeRegistry.add(gson.fromJson(listedFile.reader(), RecipeDefinition::class.java)) + } catch (err: Throwable) { + logger.error("Loading recipe definition file $listedFile", err) + } + + if (terminateLoading) { + return + } + } + } + companion object { /** * Глобальный [Interner] для [String] diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/defs/ItemReference.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/defs/ItemReference.kt new file mode 100644 index 00000000..d235bb3c --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/defs/ItemReference.kt @@ -0,0 +1,60 @@ +package ru.dbotthepony.kstarbound.defs + +import com.google.common.collect.Interner +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.JsonToken +import com.google.gson.stream.JsonWriter +import ru.dbotthepony.kstarbound.defs.item.IItemDefinition +import ru.dbotthepony.kstarbound.io.json.builder.FactoryAdapter +import ru.dbotthepony.kstarbound.io.json.builder.JsonFactory +import ru.dbotthepony.kstarbound.io.json.consumeNull +import ru.dbotthepony.kstarbound.util.ItemDescriptor + +/** + * Прототип [ItemDescriptor] в JSON файлах + */ +data class ItemReference( + val item: RegistryReference, + val count: Long = 1, + val parameters: JsonObject = JsonObject() +) { + fun makeStack(): ItemDescriptor { + return ItemDescriptor(item.value ?: return ItemDescriptor.EMPTY, count, parameters) + } + + class Factory(val stringInterner: Interner = Interner { it }) : TypeAdapterFactory { + override fun create(gson: Gson, type: TypeToken): TypeAdapter? { + if (type.rawType == ItemReference::class.java) { + return object : TypeAdapter() { + private val regular = FactoryAdapter.createFor(ItemReference::class, JsonFactory(storesJson = false, logMisses = true, asList = false), gson, stringInterner) + private val references = gson.getAdapter(TypeToken.getParameterized(RegistryReference::class.java, IItemDefinition::class.java)) as TypeAdapter> + + override fun write(out: JsonWriter, value: ItemReference?) { + if (value == null) + out.nullValue() + else + regular.write(out, value) + } + + override fun read(`in`: JsonReader): ItemReference? { + if (`in`.consumeNull()) + return null + + if (`in`.peek() == JsonToken.STRING) { + return ItemReference(references.read(`in`)) + } else { + return regular.read(`in`) + } + } + } as TypeAdapter + } + + return null + } + } +} diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/defs/player/RecipeDefinition.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/defs/player/RecipeDefinition.kt new file mode 100644 index 00000000..a42fb1f3 --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/defs/player/RecipeDefinition.kt @@ -0,0 +1,17 @@ +package ru.dbotthepony.kstarbound.defs.player + +import com.google.common.collect.ImmutableList +import com.google.common.collect.ImmutableMap +import com.google.common.collect.ImmutableSet +import ru.dbotthepony.kstarbound.defs.ItemReference +import ru.dbotthepony.kstarbound.io.json.builder.JsonFactory + +@JsonFactory +data class RecipeDefinition( + val input: ImmutableList, + val output: ItemReference, + val groups: ImmutableSet = ImmutableSet.of(), + val matchInputParameters: Boolean = false, + val duration: Double = 0.5, + val currencyInputs: ImmutableMap = ImmutableMap.of() +) diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/defs/projectile/ProjectileDefinition.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/defs/projectile/ProjectileDefinition.kt new file mode 100644 index 00000000..ba59037c --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/defs/projectile/ProjectileDefinition.kt @@ -0,0 +1,8 @@ +package ru.dbotthepony.kstarbound.defs.projectile + +import ru.dbotthepony.kstarbound.io.json.builder.JsonFactory + +@JsonFactory +data class ProjectileDefinition( + val projectileName: String +) diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/io/json/Ext.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/io/json/Ext.kt index 7c34857b..a4902f21 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/io/json/Ext.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/io/json/Ext.kt @@ -84,3 +84,12 @@ fun TypeAdapterFactory.ifString(reader: (String) -> Any): TypeAdapterFactory { } } } + +fun JsonReader.consumeNull(): Boolean { + if (peek() == JsonToken.NULL) { + nextNull() + return true + } + + return false +} diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/io/json/builder/FactoryAdapter.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/io/json/builder/FactoryAdapter.kt index 3a54183a..a0784104 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/io/json/builder/FactoryAdapter.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/io/json/builder/FactoryAdapter.kt @@ -19,13 +19,11 @@ import com.google.gson.stream.JsonWriter import it.unimi.dsi.fastutil.objects.Object2IntArrayMap import it.unimi.dsi.fastutil.objects.ObjectArraySet import org.apache.logging.log4j.LogManager -import ru.dbotthepony.kstarbound.Starbound import ru.dbotthepony.kstarbound.defs.util.enrollList import ru.dbotthepony.kstarbound.defs.util.enrollMap import ru.dbotthepony.kstarbound.defs.util.flattenJsonElement -import ru.dbotthepony.kstarbound.getValue +import ru.dbotthepony.kstarbound.io.json.consumeNull import ru.dbotthepony.kstarbound.io.json.ifString -import ru.dbotthepony.kstarbound.setValue import java.lang.reflect.Constructor import kotlin.jvm.internal.DefaultConstructorMarker import kotlin.properties.Delegates @@ -152,7 +150,12 @@ class FactoryAdapter private constructor( } } - override fun write(out: JsonWriter, value: T) { + override fun write(out: JsonWriter, value: T?) { + if (value == null) { + out.nullValue() + return + } + out.beginObject() for ((field, adapter) in types) { @@ -164,7 +167,10 @@ class FactoryAdapter private constructor( out.endObject() } - override fun read(reader: JsonReader): T { + override fun read(reader: JsonReader): T? { + if (reader.consumeNull()) + return null + // таблица присутствия значений (если значение true то на i было значение внутри json) val presentValues = BooleanArray(types.size + (if (storesJson) 1 else 0)) val readValues = arrayOfNulls(types.size + (if (storesJson) 1 else 0)) @@ -526,6 +532,44 @@ class FactoryAdapter private constructor( companion object { private val LOGGER = LogManager.getLogger() + + fun createFor(kclass: KClass, config: JsonFactory, gson: Gson, stringInterner: Interner = Interner { it }): TypeAdapter { + val builder = Builder(kclass) + val properties = kclass.declaredMembers.filterIsInstance>() + + if (config.asList) { + builder.inputAsList() + } + + builder.storesJson(config.storesJson) + builder.logMisses(config.logMisses) + builder.stringInterner = stringInterner + + if (properties.isEmpty()) { + throw IllegalArgumentException("${kclass.qualifiedName} has no valid members") + } + + val foundConstructor = kclass.primaryConstructor ?: throw NoSuchElementException("Can't determine primary constructor for ${kclass.qualifiedName}") + + if (!config.storesJson) { + for (argument in foundConstructor.parameters) { + val property = properties.first { it.name == argument.name && it.returnType.isSupertypeOf(argument.type) } + val config = property.annotations.firstOrNull { it.annotationClass == JsonPropertyConfig::class } as JsonPropertyConfig? + builder.auto(property, isFlat = config?.isFlat ?: false) + } + } else { + val params = foundConstructor.parameters + + for (i in 0 until params.size - 1) { + val argument = params[i] + val property = properties.first { it.name == argument.name && it.returnType.isSupertypeOf(argument.type) } + val config = property.annotations.firstOrNull { it.annotationClass == JsonPropertyConfig::class } as JsonPropertyConfig? + builder.auto(property, isFlat = config?.isFlat ?: false) + } + } + + return builder.build(gson) + } } class Factory(val stringInterner: Interner = Interner { it }) : TypeAdapterFactory { @@ -538,41 +582,7 @@ class FactoryAdapter private constructor( val bconfig = first[0] as JsonFactory val kclass = raw.kotlin as KClass - val builder = Builder(kclass) - val properties = kclass.declaredMembers.filterIsInstance>() - - if (bconfig.asList) { - builder.inputAsList() - } - - builder.storesJson(bconfig.storesJson) - builder.logMisses(bconfig.logMisses) - builder.stringInterner = stringInterner - - if (properties.isEmpty()) { - throw IllegalArgumentException("${kclass.qualifiedName} has no valid members") - } - - val foundConstructor = kclass.primaryConstructor ?: throw NoSuchElementException("Can't determine primary constructor for ${kclass.qualifiedName}") - - if (!bconfig.storesJson) { - for (argument in foundConstructor.parameters) { - val property = properties.first { it.name == argument.name && it.returnType.isSupertypeOf(argument.type) } - val config = property.annotations.firstOrNull { it.annotationClass == JsonPropertyConfig::class } as JsonPropertyConfig? - builder.auto(property, isFlat = config?.isFlat ?: false) - } - } else { - val params = foundConstructor.parameters - - for (i in 0 until params.size - 1) { - val argument = params[i] - val property = properties.first { it.name == argument.name && it.returnType.isSupertypeOf(argument.type) } - val config = property.annotations.firstOrNull { it.annotationClass == JsonPropertyConfig::class } as JsonPropertyConfig? - builder.auto(property, isFlat = config?.isFlat ?: false) - } - } - - return builder.build(gson) + return createFor(kclass, bconfig, gson, stringInterner) } return null