diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/Ext.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/Ext.kt index 02a2ab32..2ba6913a 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/Ext.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/Ext.kt @@ -1,5 +1,6 @@ package ru.dbotthepony.kstarbound +import com.google.common.collect.ImmutableMap import com.google.gson.GsonBuilder import com.google.gson.TypeAdapter import java.util.Arrays @@ -19,3 +20,5 @@ operator fun ThreadLocal.getValue(thisRef: Any, property: KProperty<*>): operator fun ThreadLocal.setValue(thisRef: Any, property: KProperty<*>, value: T?) { set(value) } + +operator fun ImmutableMap.Builder.set(key: K, value: V): ImmutableMap.Builder = put(key, value) diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/Starbound.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/Starbound.kt index 51bf5add..9336c472 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/Starbound.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/Starbound.kt @@ -17,8 +17,16 @@ import ru.dbotthepony.kstarbound.api.explore import ru.dbotthepony.kstarbound.defs.* import ru.dbotthepony.kstarbound.defs.image.AtlasConfiguration import ru.dbotthepony.kstarbound.defs.image.SpriteReference -import ru.dbotthepony.kstarbound.defs.item.ItemDefinition +import ru.dbotthepony.kstarbound.defs.item.ArmorItemPrototype +import ru.dbotthepony.kstarbound.defs.item.ArmorPieceType +import ru.dbotthepony.kstarbound.defs.item.CurrencyItemPrototype +import ru.dbotthepony.kstarbound.defs.item.IArmorItemDefinition +import ru.dbotthepony.kstarbound.defs.item.IFossilItemDefinition +import ru.dbotthepony.kstarbound.defs.item.IItemDefinition +import ru.dbotthepony.kstarbound.defs.item.ItemPrototype import ru.dbotthepony.kstarbound.defs.item.ItemRarity +import ru.dbotthepony.kstarbound.defs.item.ItemTooltipKind +import ru.dbotthepony.kstarbound.defs.item.LeveledStatusEffect import ru.dbotthepony.kstarbound.defs.liquid.LiquidDefinition import ru.dbotthepony.kstarbound.defs.projectile.* import ru.dbotthepony.kstarbound.defs.tile.MaterialModifier @@ -30,7 +38,7 @@ import ru.dbotthepony.kstarbound.defs.world.dungeon.DungeonWorldDef import ru.dbotthepony.kstarbound.io.* import ru.dbotthepony.kstarbound.io.json.AABBTypeAdapter import ru.dbotthepony.kstarbound.io.json.AABBiTypeAdapter -import ru.dbotthepony.kstarbound.io.json.CustomEnumTypeAdapter +import ru.dbotthepony.kstarbound.io.json.EnumAdapter import ru.dbotthepony.kstarbound.io.json.Vector2dTypeAdapter import ru.dbotthepony.kstarbound.io.json.Vector2fTypeAdapter import ru.dbotthepony.kstarbound.io.json.Vector2iTypeAdapter @@ -57,24 +65,24 @@ object Starbound { /** * Служит переменной для указания из какой папки происходит чтение asset'а в данном потоке */ - var readingFolder by ThreadLocal() + var assetFolder by ThreadLocal() private set - fun readingFolderTransformer(input: String): String { - val readingFolder = readingFolder - require(readingFolder != null) { "Not reading an asset on current thread" } + fun assetFolder(input: String): String { + val assetFolder = assetFolder + require(assetFolder != null) { "Not reading an asset on current thread" } if (input[0] == '/') return input - return assetStringInterner.intern("$readingFolder/$input") + return STRING_INTERNER.intern("$assetFolder/$input") } - fun readingFolderTransformerNullable(input: String?): String? { - require(readingFolder != null) { "Not reading an asset on current thread" } + fun assetFolderNullable(input: String?): String? { + require(assetFolder != null) { "Not reading an asset on current thread" } if (input != null) - return readingFolderTransformer(input) + return assetFolder(input) return null } @@ -83,7 +91,7 @@ object Starbound { if (input == null) return null - return input.stream().map { readingFolderTransformer(it) }.collect(ImmutableList.toImmutableList()) + return input.stream().map { assetFolder(it) }.collect(ImmutableList.toImmutableList()) } private val tiles = Object2ObjectOpenHashMap() @@ -99,7 +107,7 @@ object Starbound { private val parallax = Object2ObjectOpenHashMap() private val functions = Object2ObjectOpenHashMap() - private val items = Object2ObjectOpenHashMap() + private val items = Object2ObjectOpenHashMap() val liquidAccess: Map = Collections.unmodifiableMap(liquid) val liquidByIDAccess: Map = Collections.unmodifiableMap(liquidByID) @@ -110,21 +118,21 @@ object Starbound { val projectilesAccess: Map = Collections.unmodifiableMap(projectiles) val parallaxAccess: Map = Collections.unmodifiableMap(parallax) val functionsAccess: Map = Collections.unmodifiableMap(functions) - val itemAccess: Map = Collections.unmodifiableMap(items) + val itemAccess: Map = Collections.unmodifiableMap(items) - val assetStringInterner: Interner = Interners.newStrongInterner() + val STRING_INTERNER: Interner = Interners.newStrongInterner() - val nonnullStringTypeAdapter: TypeAdapter = object : TypeAdapter() { + val STRING_ADAPTER: TypeAdapter = object : TypeAdapter() { override fun write(out: JsonWriter, value: String) { out.value(value) } override fun read(`in`: JsonReader): String { - return assetStringInterner.intern(TypeAdapters.STRING.read(`in`)) + return STRING_INTERNER.intern(TypeAdapters.STRING.read(`in`)) } } - val stringTypeAdapter: TypeAdapter = nonnullStringTypeAdapter.nullSafe() + val NULLABLE_STRING_ADAPTER: TypeAdapter = STRING_ADAPTER.nullSafe() val gson: Gson = GsonBuilder() .enableComplexMapKeySerialization() @@ -135,7 +143,7 @@ object Starbound { .registerTypeAdapter(Color::class.java, ColorTypeAdapter.nullSafe()) // чтоб строки всегда intern'ились - .registerTypeAdapter(stringTypeAdapter) + .registerTypeAdapter(NULLABLE_STRING_ADAPTER) // math .registerTypeAdapter(AABBTypeAdapter) @@ -156,12 +164,22 @@ object Starbound { .also(RenderTemplate::registerGson) .also(TileDefinition::registerGson) .also(LiquidDefinition::registerGson) - .also(ItemDefinition::registerGson) - .also(ItemRarity::registerGson) .also(SpriteReference::registerGson) .also(AtlasConfiguration::registerGson) - .registerTypeAdapter(DamageType::class.java, CustomEnumTypeAdapter(DamageType.values()).nullSafe()) + .registerTypeAdapter(LeveledStatusEffect.ADAPTER) + + .registerTypeAdapter(ItemPrototype.ADAPTER) + .registerTypeAdapter(CurrencyItemPrototype.ADAPTER) + .registerTypeAdapter(ArmorItemPrototype.ADAPTER) + + .registerTypeAdapter(IItemDefinition.InventoryIcon.ADAPTER) + .registerTypeAdapter(IFossilItemDefinition.FossilSetDescription.ADAPTER) + .registerTypeAdapter(IArmorItemDefinition.ArmorFrames.ADAPTER) + + .registerTypeAdapter(EnumAdapter(DamageType::class, default = DamageType.NORMAL).neverNull()) + .registerTypeAdapter(EnumAdapter(ItemRarity::class).neverNull()) + .registerTypeAdapter(EnumAdapter(ItemTooltipKind::class).neverNull()) .create() @@ -170,7 +188,7 @@ object Starbound { return when (type) { Float::class.java -> TypeAdapters.FLOAT as TypeAdapter Double::class.java -> TypeAdapters.DOUBLE as TypeAdapter - String::class.java -> stringTypeAdapter as TypeAdapter + String::class.java -> NULLABLE_STRING_ADAPTER as TypeAdapter Int::class.java -> TypeAdapters.INTEGER as TypeAdapter Long::class.java -> TypeAdapters.LONG as TypeAdapter Boolean::class.java -> TypeAdapters.BOOLEAN as TypeAdapter @@ -339,14 +357,14 @@ object Starbound { } private fun loadTileMaterials(callback: (String) -> Unit) { - readingFolder = "/tiles/materials" + assetFolder = "/tiles/materials" for (fs in fileSystems) { for (listedFile in fs.explore().filter { it.isFile }.filter { it.name.endsWith(".material") }) { try { callback("Loading $listedFile") - readingFolder = listedFile.computeDirectory() + assetFolder = listedFile.computeDirectory() val tileDef = gson.fromJson(listedFile.reader(), TileDefinition::class.java) check(tiles[tileDef.materialName] == null) { "Already has material with name ${tileDef.materialName} loaded!" } @@ -364,7 +382,7 @@ object Starbound { } } - readingFolder = null + assetFolder = null } private fun loadProjectiles(callback: (String) -> Unit) { @@ -373,7 +391,7 @@ object Starbound { try { callback("Loading $listedFile") - readingFolder = listedFile.computeDirectory() + assetFolder = listedFile.computeDirectory() val def = gson.fromJson(listedFile.reader(), ConfigurableProjectile::class.java).assemble(listedFile.computeDirectory()) check(projectiles[def.projectileName] == null) { "Already has projectile with ID ${def.projectileName} loaded!" } projectiles[def.projectileName] = def @@ -388,7 +406,7 @@ object Starbound { } } - readingFolder = null + assetFolder = null } private fun loadFunctions(callback: (String) -> Unit) { @@ -397,7 +415,7 @@ object Starbound { try { callback("Loading $listedFile") - readingFolder = listedFile.computeDirectory() + assetFolder = listedFile.computeDirectory() val readObject = JsonParser.parseReader(listedFile.reader()) as JsonObject for (key in readObject.keySet()) { @@ -414,7 +432,7 @@ object Starbound { } } - readingFolder = null + assetFolder = null } private fun loadParallax(callback: (String) -> Unit) { @@ -423,7 +441,7 @@ object Starbound { try { callback("Loading $listedFile") - readingFolder = listedFile.computeDirectory() + assetFolder = listedFile.computeDirectory() val def = gson.fromJson(listedFile.reader(), ParallaxPrototype::class.java) parallax[listedFile.name.substringBefore('.')] = def } catch(err: Throwable) { @@ -436,18 +454,18 @@ object Starbound { } } - readingFolder = null + assetFolder = null } private fun loadMaterialModifiers(callback: (String) -> Unit) { - readingFolder = "/tiles/materials" + assetFolder = "/tiles/materials" for (fs in fileSystems) { for (listedFile in fs.explore().filter { it.isFile }.filter { it.name.endsWith(".matmod") }) { try { callback("Loading $listedFile") - readingFolder = listedFile.computeDirectory() + assetFolder = listedFile.computeDirectory() val tileDef = gson.fromJson(listedFile.reader(), MaterialModifier::class.java) check(tileModifiers[tileDef.modName] == null) { "Already has material with name ${tileDef.modName} loaded!" } @@ -465,7 +483,7 @@ object Starbound { } } - readingFolder = null + assetFolder = null } private fun loadLiquidDefinitions(callback: (String) -> Unit) { @@ -474,7 +492,7 @@ object Starbound { try { callback("Loading $listedFile") - readingFolder = listedFile.computeDirectory() + assetFolder = listedFile.computeDirectory() val liquidDef = gson.fromJson(listedFile.reader(), LiquidDefinition::class.java) check(liquid.put(liquidDef.name, liquidDef) == null) { "Already has liquid with name ${liquidDef.name} loaded!" } @@ -490,22 +508,42 @@ object Starbound { } } - readingFolder = null + assetFolder = null } private fun loadItemDefinitions(callback: (String) -> Unit) { - val files = listOf(".item", ".currency", ".head", ".chest", ".legs", ".activeitem") + val files = listOf(".item", ".currency", ".head", ".chest", ".legs", ".back", ".activeitem") for (fs in fileSystems) { for (listedFile in fs.explore().filter { it.isFile }.filter { f -> files.any { f.name.endsWith(it) } }) { try { callback("Loading $listedFile") - readingFolder = listedFile.computeDirectory() - ItemDefinition.ADAPTER.currentSymbolicName = listedFile.computeFullPath() - val def = gson.fromJson(listedFile.reader(), ItemDefinition::class.java) + assetFolder = listedFile.computeDirectory() - check(items.put(def.itemName, def) == null) { "Already has item with name ${def.itemName} loaded!" } + if (listedFile.name.endsWith(".item")) { + val def = gson.fromJson(listedFile.reader(), ItemPrototype::class.java) + check(items.put(def.itemName, def.assemble()) == null) { "Already has item with name ${def.itemName} loaded!" } + } else if (listedFile.name.endsWith(".currency")) { + val def = gson.fromJson(listedFile.reader(), CurrencyItemPrototype::class.java) + check(items.put(def.itemName, def.assemble()) == null) { "Already has item with name ${def.itemName} loaded!" } + } else if (listedFile.name.endsWith(".head")) { + val def = gson.fromJson(listedFile.reader(), ArmorItemPrototype::class.java) + def.armorType = ArmorPieceType.HEAD + check(items.put(def.itemName, def.assemble()) == null) { "Already has item with name ${def.itemName} loaded!" } + } else if (listedFile.name.endsWith(".chest")) { + val def = gson.fromJson(listedFile.reader(), ArmorItemPrototype::class.java) + def.armorType = ArmorPieceType.CHEST + check(items.put(def.itemName, def.assemble()) == null) { "Already has item with name ${def.itemName} loaded!" } + } else if (listedFile.name.endsWith(".legs")) { + val def = gson.fromJson(listedFile.reader(), ArmorItemPrototype::class.java) + def.armorType = ArmorPieceType.LEGS + check(items.put(def.itemName, def.assemble()) == null) { "Already has item with name ${def.itemName} loaded!" } + } else if (listedFile.name.endsWith(".back")) { + val def = gson.fromJson(listedFile.reader(), ArmorItemPrototype::class.java) + def.armorType = ArmorPieceType.BACK + check(items.put(def.itemName, def.assemble()) == null) { "Already has item with name ${def.itemName} loaded!" } + } } catch (err: Throwable) { LOGGER.error("Loading item definition file $listedFile", err) } @@ -516,6 +554,6 @@ object Starbound { } } - readingFolder = null + assetFolder = null } } diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/defs/IThingWithDescription.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/defs/IThingWithDescription.kt new file mode 100644 index 00000000..8c26f34b --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/defs/IThingWithDescription.kt @@ -0,0 +1,24 @@ +package ru.dbotthepony.kstarbound.defs + +interface IThingWithDescription { + /** + * Краткое описание штуки. Несмотря на то, что название свойства подразумевает "описание", + * на самом деле данное поле отвечает за название штуки. + * + * Примеры: + * * Microwave Oven + * * Copper Ore + * * Poptop + */ + val shortdescription: String + + /** + * Полное описание штуки. Оно отображается игроку, когда последний наводит курсор на штуку. + * + * Примеры: + * * A microwave. For when you're hungry enough to nuke your food. + * * Copper ore. Can be used for smelting. + * * The Poptop hums beautifully to confuse its prey. + */ + val description: String +} diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/defs/ImmutableEnroller.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/defs/ImmutableEnroller.kt index d2fa8f5a..153022a0 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/defs/ImmutableEnroller.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/defs/ImmutableEnroller.kt @@ -4,7 +4,7 @@ import com.google.common.collect.ImmutableList import com.google.common.collect.ImmutableMap /** - * Возвращает глубокую неизменяемую копию [input] примитивов/List'ов/Map'ов + * Возвращает глубокую, неизменяемую копию [input] примитивов/List'ов/Map'ов */ fun enrollList(input: List, interner: (String) -> String = String::intern): ImmutableList { val builder = ImmutableList.builder() @@ -21,7 +21,7 @@ fun enrollList(input: List, interner: (String) -> String = String::intern): } /** - * Возвращает глубокую неизменяемую копию [input] примитивов/List'ов/Map'ов + * Возвращает глубокую, неизменяемую копию [input] примитивов/List'ов/Map'ов */ fun enrollMap(input: Map, interner: (String) -> String = String::intern): ImmutableMap { val builder = ImmutableMap.builder() diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/defs/JsonFunction.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/defs/JsonFunction.kt index 1d240bd3..7e5a6d86 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/defs/JsonFunction.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/defs/JsonFunction.kt @@ -6,7 +6,7 @@ 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.kstarbound.io.json.CustomEnumTypeAdapter +import ru.dbotthepony.kstarbound.io.json.EnumAdapter import ru.dbotthepony.kstarbound.io.json.IStringSerializable import ru.dbotthepony.kstarbound.io.json.Vector2dTypeAdapter import ru.dbotthepony.kvector.vector.ndouble.Vector2d @@ -29,7 +29,7 @@ enum class JsonFunctionInterpolation(vararg aliases: String) : IStringSerializab } companion object { - val ADAPTER: TypeAdapter = CustomEnumTypeAdapter(values()).nullSafe() + val ADAPTER: TypeAdapter = EnumAdapter(JsonFunctionInterpolation::class).neverNull() } } @@ -51,7 +51,7 @@ enum class JsonFunctionConstraint(vararg aliases: String) : IStringSerializable } companion object { - val ADAPTER: TypeAdapter = CustomEnumTypeAdapter(values()).nullSafe() + val ADAPTER: TypeAdapter = EnumAdapter(JsonFunctionConstraint::class).neverNull() } } diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/defs/RawPrototype.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/defs/RawPrototype.kt index 9ae9ed9f..46a72306 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/defs/RawPrototype.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/defs/RawPrototype.kt @@ -16,7 +16,7 @@ import ru.dbotthepony.kstarbound.io.json.INativeJsonHolder */ abstract class RawPrototype, ASSEMBLED : AssembledPrototype> : INativeJsonHolder { val json = Object2ObjectArrayMap() - fun enroll() = enrollMap(json, Starbound.assetStringInterner::intern) + fun enroll() = enrollMap(json, Starbound.STRING_INTERNER::intern) abstract fun assemble(directory: String = ""): ASSEMBLED override fun acceptJson(json: MutableMap) { diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/defs/image/AtlasConfiguration.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/defs/image/AtlasConfiguration.kt index 00ad6daf..2105916c 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/defs/image/AtlasConfiguration.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/defs/image/AtlasConfiguration.kt @@ -304,7 +304,7 @@ class AtlasConfiguration private constructor( return EMPTY } - val ADAPTER: TypeAdapter = Starbound.stringTypeAdapter.transform(read = read@{ get(it ?: return@read it as AtlasConfiguration?) }, write = write@{ it?.name }) + val ADAPTER: TypeAdapter = Starbound.NULLABLE_STRING_ADAPTER.transform(read = read@{ get(it ?: return@read it as AtlasConfiguration?) }, write = write@{ it?.name }) fun registerGson(gsonBuilder: GsonBuilder) { gsonBuilder.registerTypeAdapter(ADAPTER) diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/defs/image/ImageReference.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/defs/image/ImageReference.kt index 03d086da..6fd2079c 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/defs/image/ImageReference.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/defs/image/ImageReference.kt @@ -30,7 +30,7 @@ data class ImageReference( override fun read(`in`: JsonReader): ImageReference { if (`in`.peek() == JsonToken.STRING) { - val image = Starbound.readingFolderTransformer(`in`.nextString()) + val image = Starbound.assetFolder(`in`.nextString()) if (image.contains(':')) { throw JsonSyntaxException("Expected atlas/image reference, but got sprite reference: $image") diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/defs/image/SpriteReference.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/defs/image/SpriteReference.kt index fed0737e..9e55ec30 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/defs/image/SpriteReference.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/defs/image/SpriteReference.kt @@ -30,7 +30,7 @@ data class SpriteReference( } override fun read(`in`: JsonReader): SpriteReference { - return parse(Starbound.readingFolderTransformer(`in`.nextString())) + return parse(Starbound.assetFolder(`in`.nextString())) } fun registerGson(gsonBuilder: GsonBuilder) { diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/defs/item/ArmorItemDefinition.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/defs/item/ArmorItemDefinition.kt new file mode 100644 index 00000000..ed8e5c6b --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/defs/item/ArmorItemDefinition.kt @@ -0,0 +1,32 @@ +package ru.dbotthepony.kstarbound.defs.item + +data class ArmorItemDefinition( + override val shortdescription: String, + override val description: String, + override val itemName: String, + override val price: Long, + override val rarity: ItemRarity, + override val category: String?, + override val inventoryIcon: List?, + override val itemTags: List, + override val learnBlueprintsOnPickup: List, + override val maxStack: Long, + override val eventCategory: String?, + override val consumeOnPickup: Boolean, + override val pickupQuestTemplates: List, + override val scripts: List, + override val tooltipKind: ItemTooltipKind, + override val twoHanded: Boolean, + override val radioMessagesOnPickup: List, + override val fuelAmount: Long?, + + override val colorOptions: List>, + override val maleFrames: IArmorItemDefinition.IArmorFrames, + override val femaleFrames: IArmorItemDefinition.IArmorFrames, + override val level: Double, + override val leveledStatusEffects: List, + + override val armorType: ArmorPieceType, + + val json: Map, +) : IArmorItemDefinition diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/defs/item/ArmorItemPrototype.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/defs/item/ArmorItemPrototype.kt new file mode 100644 index 00000000..0240250b --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/defs/item/ArmorItemPrototype.kt @@ -0,0 +1,67 @@ +package ru.dbotthepony.kstarbound.defs.item + +import ru.dbotthepony.kstarbound.Starbound +import ru.dbotthepony.kstarbound.defs.enrollMap +import ru.dbotthepony.kstarbound.io.json.BuilderAdapter +import ru.dbotthepony.kstarbound.io.json.asJsonObject +import ru.dbotthepony.kstarbound.io.json.asList +import ru.dbotthepony.kstarbound.io.json.neverNull +import ru.dbotthepony.kstarbound.util.NotNullVar + +class ArmorItemPrototype : ItemPrototype(), IArmorItemDefinition { + override var colorOptions: List> = listOf() + override var maleFrames: IArmorItemDefinition.ArmorFrames by NotNullVar() + override var femaleFrames: IArmorItemDefinition.ArmorFrames by NotNullVar() + override var level: Double = 1.0 + override var leveledStatusEffects: List = listOf() + + override var armorType: ArmorPieceType by NotNullVar() + + init { + maxStack = 1L + } + + override fun assemble(): IItemDefinition { + return ArmorItemDefinition( + shortdescription = shortdescription, + description = description, + itemName = itemName, + price = price, + rarity = rarity, + category = category, + inventoryIcon = inventoryIcon, + itemTags = itemTags, + learnBlueprintsOnPickup = learnBlueprintsOnPickup, + maxStack = maxStack, + eventCategory = eventCategory, + consumeOnPickup = consumeOnPickup, + pickupQuestTemplates = pickupQuestTemplates, + scripts = scripts, + tooltipKind = tooltipKind, + twoHanded = twoHanded, + radioMessagesOnPickup = radioMessagesOnPickup, + fuelAmount = fuelAmount, + + json = enrollMap(json), + + colorOptions = colorOptions, + maleFrames = maleFrames, + femaleFrames = femaleFrames, + level = level, + leveledStatusEffects = leveledStatusEffects, + + armorType = armorType, + ) + } + + companion object { + val ADAPTER = BuilderAdapter.Builder(::ArmorItemPrototype) + .also { addFields(it as BuilderAdapter.Builder) } // безопасность: свойства родительского класса объявлены как final + .add(ArmorItemPrototype::colorOptions, Starbound.NULLABLE_STRING_ADAPTER.neverNull().asJsonObject().asList()) + .auto(ArmorItemPrototype::maleFrames) + .auto(ArmorItemPrototype::femaleFrames) + .auto(ArmorItemPrototype::level) + .autoList(ArmorItemPrototype::leveledStatusEffects) + .build() + } +} diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/defs/item/ArmorPieceType.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/defs/item/ArmorPieceType.kt new file mode 100644 index 00000000..e942eb10 --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/defs/item/ArmorPieceType.kt @@ -0,0 +1,26 @@ +package ru.dbotthepony.kstarbound.defs.item + +/** + * Тип брони. Более формально, в какой слот надевается данный предмет + */ +enum class ArmorPieceType { + /** + * Шлем + */ + HEAD, + + /** + * Нагрудник + */ + CHEST, + + /** + * Поножи + */ + LEGS, + + /** + * Плащ/рюкзак/прочее + */ + BACK +} diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/defs/item/CurrencyItemDefinition.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/defs/item/CurrencyItemDefinition.kt new file mode 100644 index 00000000..52704e1b --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/defs/item/CurrencyItemDefinition.kt @@ -0,0 +1,32 @@ +package ru.dbotthepony.kstarbound.defs.item + +data class CurrencyItemDefinition( + override val shortdescription: String, + override val description: String, + override val itemName: String, + override val price: Long, + override val rarity: ItemRarity, + override val category: String?, + override val inventoryIcon: List?, + override val itemTags: List, + override val learnBlueprintsOnPickup: List, + override val maxStack: Long, + override val eventCategory: String?, + override val consumeOnPickup: Boolean, + override val pickupQuestTemplates: List, + override val scripts: List, + override val tooltipKind: ItemTooltipKind, + override val twoHanded: Boolean, + override val radioMessagesOnPickup: List, + override val fuelAmount: Long?, + + override val pickupSoundsSmall: List, + override val pickupSoundsMedium: List, + override val pickupSoundsLarge: List, + override val smallStackLimit: Long, + override val mediumStackLimit: Long, + override val currency: String, + override val value: Long, + + val json: Map, +) : ICurrencyItemDefinition diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/defs/item/CurrencyItemPrototype.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/defs/item/CurrencyItemPrototype.kt new file mode 100644 index 00000000..22cc14e8 --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/defs/item/CurrencyItemPrototype.kt @@ -0,0 +1,65 @@ +package ru.dbotthepony.kstarbound.defs.item + +import ru.dbotthepony.kstarbound.defs.enrollMap +import ru.dbotthepony.kstarbound.io.json.BuilderAdapter +import ru.dbotthepony.kstarbound.util.NotNullVar + +class CurrencyItemPrototype : ItemPrototype(), ICurrencyItemDefinition { + override var pickupSoundsSmall: List = listOf() + override var pickupSoundsMedium: List = listOf() + override var pickupSoundsLarge: List = listOf() + override var smallStackLimit: Long by NotNullVar() + override var mediumStackLimit: Long by NotNullVar() + override var currency: String by NotNullVar() + override var value: Long by NotNullVar() + + init { + maxStack = 16777216L + } + + override fun assemble(): IItemDefinition { + return CurrencyItemDefinition( + shortdescription = shortdescription, + description = description, + itemName = itemName, + price = price, + rarity = rarity, + category = category, + inventoryIcon = inventoryIcon, + itemTags = itemTags, + learnBlueprintsOnPickup = learnBlueprintsOnPickup, + maxStack = maxStack, + eventCategory = eventCategory, + consumeOnPickup = consumeOnPickup, + pickupQuestTemplates = pickupQuestTemplates, + scripts = scripts, + tooltipKind = tooltipKind, + twoHanded = twoHanded, + radioMessagesOnPickup = radioMessagesOnPickup, + fuelAmount = fuelAmount, + + json = enrollMap(json), + + pickupSoundsSmall = pickupSoundsSmall, + pickupSoundsMedium = pickupSoundsMedium, + pickupSoundsLarge = pickupSoundsLarge, + smallStackLimit = smallStackLimit, + mediumStackLimit = mediumStackLimit, + currency = currency, + value = value, + ) + } + + companion object { + val ADAPTER = BuilderAdapter.Builder(::CurrencyItemPrototype) + .also { addFields(it as BuilderAdapter.Builder) } // безопасность: свойства родительского класса объявлены как final + .autoList(CurrencyItemPrototype::pickupSoundsSmall) + .autoList(CurrencyItemPrototype::pickupSoundsMedium) + .autoList(CurrencyItemPrototype::pickupSoundsLarge) + .auto(CurrencyItemPrototype::smallStackLimit) + .auto(CurrencyItemPrototype::mediumStackLimit) + .auto(CurrencyItemPrototype::currency) + .auto(CurrencyItemPrototype::value) + .build() + } +} diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/defs/item/IArmorItemDefinition.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/defs/item/IArmorItemDefinition.kt new file mode 100644 index 00000000..08b4df32 --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/defs/item/IArmorItemDefinition.kt @@ -0,0 +1,50 @@ +package ru.dbotthepony.kstarbound.defs.item + +import ru.dbotthepony.kstarbound.Starbound +import ru.dbotthepony.kstarbound.io.json.FactoryAdapter +import ru.dbotthepony.kstarbound.io.json.ifString + +interface IArmorItemDefinition : ILeveledItemDefinition { + /** + * @see ArmorPieceType + */ + val armorType: ArmorPieceType + + /** + * Варианты покраски (???) + */ + val colorOptions: List> + + /** + * Визуальные кадры анимации, когда надето на гуманоида мужского пола + */ + val maleFrames: IArmorFrames + + /** + * Визуальные кадры анимации, когда надето на гуманоида женского пола + */ + val femaleFrames: IArmorFrames + + interface IArmorFrames { + val body: String + val backSleeve: String? + val frontSleeve: String? + } + + data class ArmorFrames( + override val body: String, + override val backSleeve: String?, + override val frontSleeve: String?, + ) : IArmorFrames { + companion object { + val ADAPTER = FactoryAdapter.Builder( + ArmorFrames::class, + ArmorFrames::body, + ArmorFrames::backSleeve, + ArmorFrames::frontSleeve, + ) + .build() + .ifString { ArmorFrames(Starbound.assetFolder(it), null, null) } + } + } +} diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/defs/item/ICurrencyItemDefinition.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/defs/item/ICurrencyItemDefinition.kt new file mode 100644 index 00000000..ebe991ca --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/defs/item/ICurrencyItemDefinition.kt @@ -0,0 +1,38 @@ +package ru.dbotthepony.kstarbound.defs.item + +interface ICurrencyItemDefinition : IItemDefinition { + /** + * Звуки при поднятии "малого" количества предметов. Не имеет никакого смысла без [smallStackLimit] + */ + val pickupSoundsSmall: List + + /** + * Звуки при поднятии "среднего" количества предметов. Не имеет никакого смысла без [mediumStackLimit] + */ + val pickupSoundsMedium: List + + /** + * Звуки при поднятии "большого" количества предметов. Не имеет никакого смысла без [smallStackLimit] и без [mediumStackLimit] + */ + val pickupSoundsLarge: List + + /** + * Количество предметов ниже или равному данному значению проиграет звук [pickupSoundsSmall] + */ + val smallStackLimit: Long + + /** + * Количество предметов ниже или равному данному значению (но не меньше [smallStackLimit]) проиграет звук [pickupSoundsMedium] + */ + val mediumStackLimit: Long + + /** + * ID валюты + */ + val currency: String + + /** + * Ценность одного предмета в [currency] + */ + val value: Long +} diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/defs/item/IFossilItemDefinition.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/defs/item/IFossilItemDefinition.kt new file mode 100644 index 00000000..c1212780 --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/defs/item/IFossilItemDefinition.kt @@ -0,0 +1,70 @@ +package ru.dbotthepony.kstarbound.defs.item + +import ru.dbotthepony.kstarbound.defs.IThingWithDescription +import ru.dbotthepony.kstarbound.io.json.FactoryAdapter +import ru.dbotthepony.kvector.vector.ndouble.Vector2d + +interface IFossilItemDefinition : IItemDefinition { + /** + * Используется в костях-ископаемых + */ + val race: String + val displayImage: String + val displayoffset: Vector2d + + /** + * Используется в костях-ископаемых + */ + val fossilSetName: String + + /** + * Используется в костях-ископаемых + */ + val setIndex: Int + + /** + * Используется в костях-ископаемых + */ + val setCount: Int + + /** + * Используется в костях-ископаемых + */ + val setCollectables: Map + + /** + * Используется в костях-ископаемых + */ + val completeFossilIcon: String? + + /** + * Используется в костях-ископаемых + */ + val completeFossilObject: String? + + /** + * Используется в костях-ископаемых + */ + val completeSetDescriptions: IFossilSetDescription? + + interface IFossilSetDescription : IThingWithDescription { + /** + * Цена в пикселях + */ + val price: Long + } + + data class FossilSetDescription( + override val price: Long = 0L, + override val shortdescription: String = "...", + override val description: String = "..." + ) : IFossilSetDescription { + companion object { + val ADAPTER = FactoryAdapter.Builder( + FossilSetDescription::class, + FossilSetDescription::price, + FossilSetDescription::shortdescription, + FossilSetDescription::description).build() + } + } +} diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/defs/item/IItemDefinition.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/defs/item/IItemDefinition.kt new file mode 100644 index 00000000..4f645076 --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/defs/item/IItemDefinition.kt @@ -0,0 +1,105 @@ +package ru.dbotthepony.kstarbound.defs.item + +import ru.dbotthepony.kstarbound.Starbound +import ru.dbotthepony.kstarbound.defs.IThingWithDescription +import ru.dbotthepony.kstarbound.defs.image.SpriteReference +import ru.dbotthepony.kstarbound.io.json.FactoryAdapter +import ru.dbotthepony.kstarbound.io.json.ifString + +interface IItemDefinition : IThingWithDescription { + /** + * Внутреннее имя предмета (ID). + * Не путать с именем предмета! + * + * @see shortdescription + * @see description + */ + val itemName: String + + /** + * Цена в пикселях + */ + val price: Long + + /** + * Редкость как [ItemRarity] + */ + val rarity: ItemRarity + + /** + * Категория предмета, определяет, в какую вкладку инвентаря оно попадает + */ + val category: String? + + /** + * Иконка в инвентаре, относительный и абсолютный пути + */ + val inventoryIcon: List? + + interface IInventoryIcon { + val image: SpriteReference + } + + data class InventoryIcon( + override val image: SpriteReference + ) : IInventoryIcon { + companion object { + val ADAPTER = FactoryAdapter.Builder(InventoryIcon::class, InventoryIcon::image).build().ifString { InventoryIcon(SpriteReference.parse(Starbound.assetFolder(it))) } + } + } + + /** + * Теги предмета + */ + val itemTags: List + + /** + * При подборе предмета мгновенно заставляет игрока изучить эти рецепты крафта + */ + val learnBlueprintsOnPickup: List + + /** + * Максимальное количество предмета в стопке + */ + val maxStack: Long + + /** + * snip + */ + val eventCategory: String? + + /** + * Заставляет предмет "использовать" сразу же при подборе + */ + val consumeOnPickup: Boolean + + /** + * Запускает следующие квест(ы) при подборе + */ + val pickupQuestTemplates: List + + /** + * Lua скрипты для выполнения + */ + val scripts: List + + /** + * это где либо ещё применяется кроме брони? + */ + val tooltipKind: ItemTooltipKind + + /** + * Занимает ли предмет обе руки + */ + val twoHanded: Boolean + + /** + * Заставляет SAIL/прочих болтать при подборе предмета в первый раз + */ + val radioMessagesOnPickup: List + + /** + * Топливо корабля + */ + val fuelAmount: Long? +} diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/defs/item/ILeveledItemDefinition.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/defs/item/ILeveledItemDefinition.kt new file mode 100644 index 00000000..0e6ed158 --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/defs/item/ILeveledItemDefinition.kt @@ -0,0 +1,13 @@ +package ru.dbotthepony.kstarbound.defs.item + +interface ILeveledItemDefinition : IItemDefinition { + /** + * Изначальный уровень предмета, может быть изменён позднее чем угодно + */ + val level: Double + + /** + * Эффекты предмета, растущие с уровнем + */ + val leveledStatusEffects: List +} diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/defs/item/ILeveledStatusEffect.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/defs/item/ILeveledStatusEffect.kt new file mode 100644 index 00000000..795a646b --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/defs/item/ILeveledStatusEffect.kt @@ -0,0 +1,26 @@ +package ru.dbotthepony.kstarbound.defs.item + +import ru.dbotthepony.kstarbound.io.json.FactoryAdapter + +interface ILeveledStatusEffect { + val levelFunction: String + val stat: String + val baseMultiplier: Double + val amount: Double +} + +data class LeveledStatusEffect( + override val levelFunction: String, + override val stat: String, + override val baseMultiplier: Double = 1.0, + override val amount: Double = 0.0, +) : ILeveledStatusEffect { + companion object { + val ADAPTER = FactoryAdapter.Builder(LeveledStatusEffect::class, + LeveledStatusEffect::levelFunction, + LeveledStatusEffect::stat, + LeveledStatusEffect::baseMultiplier, + LeveledStatusEffect::amount, + ).build() + } +} diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/defs/item/ItemDefinition.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/defs/item/ItemDefinition.kt index 05035001..56acab8a 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/defs/item/ItemDefinition.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/defs/item/ItemDefinition.kt @@ -1,354 +1,24 @@ package ru.dbotthepony.kstarbound.defs.item -import com.google.gson.GsonBuilder -import ru.dbotthepony.kstarbound.Starbound -import ru.dbotthepony.kstarbound.defs.image.SpriteReference -import ru.dbotthepony.kstarbound.io.json.FactoryAdapter -import ru.dbotthepony.kstarbound.io.json.ListAdapter -import ru.dbotthepony.kstarbound.io.json.asJsonObject -import ru.dbotthepony.kstarbound.io.json.asList -import ru.dbotthepony.kstarbound.io.json.ifString -import ru.dbotthepony.kstarbound.registerTypeAdapter -import ru.dbotthepony.kvector.vector.ndouble.Vector2d - data class ItemDefinition( - /** - * Внутреннее имя предмета, как строка - */ - val itemName: String, + override val shortdescription: String, + override val description: String, + override val itemName: String, + override val price: Long, + override val rarity: ItemRarity, + override val category: String?, + override val inventoryIcon: List?, + override val itemTags: List, + override val learnBlueprintsOnPickup: List, + override val maxStack: Long, + override val eventCategory: String?, + override val consumeOnPickup: Boolean, + override val pickupQuestTemplates: List, + override val scripts: List, + override val tooltipKind: ItemTooltipKind, + override val twoHanded: Boolean, + override val radioMessagesOnPickup: List, + override val fuelAmount: Long?, - /** - * Цена в пикселях - */ - val price: Long = 0L, - - /** - * Редкость как [ItemRarity] - */ - val rarity: ItemRarity = ItemRarity.COMMON, - - /** - * Категория предмета, определяет, в какую вкладку инвентаря оно попадает - */ - val category: String? = null, - - /** - * Иконка в инвентаре, относительный и абсолютный пути - */ - val inventoryIcon: List? = null, - - /** - * Описание предмета - */ - val description: String = "...", - - /** - * Название предмета - */ - val shortdescription: String = "...", - - /** - * Теги предмета - */ - val itemTags: List = listOf(), - - /** - * При подборе предмета мгновенно заставляет игрока изучить эти рецепты крафта - */ - val learnBlueprintsOnPickup: List = listOf(), - - /** - * Максимальное количество предмета в стопке, по умолчанию 9999 - */ - val maxStack: Long = 9999L, - - /** - * snip - */ - val eventCategory: String? = null, - - /** - * Заставляет предмет "использовать" сразу же при подборе - */ - val consumeOnPickup: Boolean = false, - - /** - * Запускает следующие квест(ы) при подборе - */ - val pickupQuestTemplates: List = listOf(), - - /** - * Используется в костях-ископаемых - */ - val race: String? = null, - val displayImage: String? = null, - val displayoffset: Vector2d? = null, - - /** - * Используется в костях-ископаемых - */ - val fossilSetName: String? = null, - - /** - * Используется в костях-ископаемых - */ - val setIndex: Int? = null, - - /** - * Используется в костях-ископаемых - */ - val setCount: Int? = null, - - /** - * Используется в костях-ископаемых - */ - val setCollectables: Map? = null, - - /** - * Используется в костях-ископаемых - */ - val completeFossilIcon: String? = null, - - /** - * Используется в костях-ископаемых - */ - val completeFossilObject: String? = null, - - /** - * Используется в костях-ископаемых - */ - val completeSetDescriptions: FossilSetDescription? = null, - - /** - * Заставляет SAIL/прочих болтать при подборе предмета в первый раз - */ - val radioMessagesOnPickup: List = listOf(), - - /** - * Топливо корабля - */ - val fuelAmount: Long? = null, - - // ---------------- - // Поля ниже были видны только в файлах валюты - // ---------------- - - /** - * Звуки при поднятии "малого" количества предметов. Не имеет никакого смысла без [smallStackLimit] - */ - val pickupSoundsSmall: List = listOf(), - - /** - * Звуки при поднятии "среднего" количества предметов. Не имеет никакого смысла без [mediumStackLimit] - */ - val pickupSoundsMedium: List = listOf(), - - /** - * Звуки при поднятии "большого" количества предметов. Не имеет никакого смысла без [smallStackLimit] и без [mediumStackLimit] - */ - val pickupSoundsLarge: List = listOf(), - - /** - * Количество предметов ниже или равному данному значению проиграет звук [pickupSoundsSmall] - */ - val smallStackLimit: Long? = null, - - /** - * Количество предметов ниже или равному данному значению (но не меньше [smallStackLimit]) проиграет звук [pickupSoundsMedium] - */ - val mediumStackLimit: Long? = null, - - /** - * Превращает предмет в валюту - */ - val currency: String? = null, - - /** - * Ценность в [currency] - */ - val value: Long? = null, - - // ---------------- - // /Валюта - // ---------------- - - /** - * Lua скрипты для выполнения - */ - val scripts: List = listOf(), - val animationScripts: List = listOf(), - - // ---------------- - // Броня - // ---------------- - - /** - * это где либо ещё применяется кроме брони? - */ - val tooltipKind: String? = null, - - /** - * Изначальный уровень, может быть изменён позднее чем угодно - */ - val level: Int? = null, - - /** - * Эффекты предмета, растущие с уровнем - */ - val leveledStatusEffects: List = listOf(), - - /** - * Варианты покраски (???) - */ - val colorOptions: List> = listOf(), - - /** - * Визуальные кадры анимации, когда надето на гуманоида мужского пола - */ - val maleFrames: ArmorFrames? = null, - - /** - * Визуальные кадры анимации, когда надето на гуманоида женского пола - */ - val femaleFrames: ArmorFrames? = null, - - // ---------------- - // /Броня - // ---------------- - - // ---------------- - // activeitem - // ---------------- - - // TODO: это указатель на структуру - val animation: String? = null, - - /** - * Занимает ли предмет обе руки - */ - val twoHanded: Boolean = false, - - // ---------------- - // /activeitem - // ---------------- - - /** - * Прототип данного предмета, как JSON структура - * - * Имеет смысл только для Lua скриптов - */ - val json: Map, -) { - data class FossilSetDescription( - val price: Long = 0L, - val shortdescription: String = "...", - val description: String = "..." - ) - - data class ArmorFrames( - val body: String, - val backSleeve: String?, - val frontSleeve: String?, - ) - - data class StatusEffect( - val levelFunction: String, - val stat: String, - val baseMultiplier: Double = 1.0, - val amount: Double = 0.0, - ) - - data class InventoryIcon( - val image: SpriteReference - ) - - companion object { - val INVENTORY_ICON_ADAPTER = FactoryAdapter.Builder(InventoryIcon::class) - .auto(InventoryIcon::image) - .build() - - val ADAPTER = FactoryAdapter.Builder(ItemDefinition::class) - .auto(ItemDefinition::itemName) - .auto(ItemDefinition::price) - .auto(ItemDefinition::rarity) - .auto(ItemDefinition::category) - .add(ItemDefinition::inventoryIcon, ListAdapter(INVENTORY_ICON_ADAPTER).ifString { listOf(InventoryIcon(SpriteReference.parse(Starbound.readingFolderTransformer(it)))) }.nullSafe()) - .auto(ItemDefinition::description) - .auto(ItemDefinition::shortdescription) - - .autoList(ItemDefinition::itemTags) - .autoList(ItemDefinition::learnBlueprintsOnPickup) - - .auto(ItemDefinition::maxStack) - .auto(ItemDefinition::eventCategory) - .auto(ItemDefinition::consumeOnPickup) - .autoList(ItemDefinition::pickupQuestTemplates) - - .auto(ItemDefinition::race) - .auto(ItemDefinition::displayImage, transformer = Starbound::readingFolderTransformerNullable) - .auto(ItemDefinition::displayoffset) - .auto(ItemDefinition::fossilSetName) - .auto(ItemDefinition::setIndex) - .auto(ItemDefinition::setCount) - .mapAsObject(ItemDefinition::setCollectables, String::class) - .auto(ItemDefinition::completeFossilIcon) - .auto(ItemDefinition::completeFossilObject) - - .auto(ItemDefinition::completeSetDescriptions) - .autoList(ItemDefinition::radioMessagesOnPickup) - .auto(ItemDefinition::fuelAmount) - - .autoList(ItemDefinition::pickupSoundsSmall) - .autoList(ItemDefinition::pickupSoundsMedium) - .autoList(ItemDefinition::pickupSoundsLarge) - .auto(ItemDefinition::smallStackLimit) - .auto(ItemDefinition::mediumStackLimit) - .auto(ItemDefinition::currency) - .auto(ItemDefinition::value) - - .autoList(ItemDefinition::scripts, transformer = Starbound::readingFolderListTransformer) - .autoList(ItemDefinition::animationScripts, transformer = Starbound::readingFolderListTransformer) - - .auto(ItemDefinition::tooltipKind) - .auto(ItemDefinition::level) - .autoList(ItemDefinition::leveledStatusEffects) - .add(ItemDefinition::colorOptions, Starbound.nonnullStringTypeAdapter.asJsonObject().asList()) - .auto(ItemDefinition::maleFrames) - .auto(ItemDefinition::femaleFrames) - - .auto(ItemDefinition::animation, transformer = Starbound::readingFolderTransformerNullable) - .auto(ItemDefinition::twoHanded) - - .storesJson() - - .build() - - val FOSSIL_ADAPTER = FactoryAdapter.Builder(FossilSetDescription::class) - .auto(FossilSetDescription::price) - .auto(FossilSetDescription::shortdescription) - .auto(FossilSetDescription::description) - .build() - - val ARMOR_FRAMES_ADAPTER = FactoryAdapter.Builder(ArmorFrames::class) - .auto(ArmorFrames::body, transformer = Starbound::readingFolderTransformer) - .auto(ArmorFrames::backSleeve, transformer = Starbound::readingFolderTransformerNullable) - .auto(ArmorFrames::frontSleeve, transformer = Starbound::readingFolderTransformerNullable) - .build() - .ifString { ArmorFrames(Starbound.readingFolderTransformer(it), null, null) } - - val STATUS_EFFECT_ADAPTER = FactoryAdapter.Builder(StatusEffect::class) - .auto(StatusEffect::levelFunction) - .auto(StatusEffect::stat) - .auto(StatusEffect::baseMultiplier) - .auto(StatusEffect::amount) - .build() - - fun registerGson(gsonBuilder: GsonBuilder) { - gsonBuilder.registerTypeAdapter(ADAPTER) - gsonBuilder.registerTypeAdapter(FOSSIL_ADAPTER) - gsonBuilder.registerTypeAdapter(ARMOR_FRAMES_ADAPTER) - gsonBuilder.registerTypeAdapter(STATUS_EFFECT_ADAPTER) - gsonBuilder.registerTypeAdapter(INVENTORY_ICON_ADAPTER) - } - } -} + val json: Map +) : IItemDefinition diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/defs/item/ItemPrototype.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/defs/item/ItemPrototype.kt new file mode 100644 index 00000000..0311ace8 --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/defs/item/ItemPrototype.kt @@ -0,0 +1,87 @@ +package ru.dbotthepony.kstarbound.defs.item + +import ru.dbotthepony.kstarbound.defs.enrollMap +import ru.dbotthepony.kstarbound.io.json.BuilderAdapter +import ru.dbotthepony.kstarbound.io.json.INativeJsonHolder +import ru.dbotthepony.kstarbound.util.NotNullVar + +open class ItemPrototype : IItemDefinition, INativeJsonHolder { + final override var shortdescription: String = "..." + final override var description: String = "..." + final override var itemName: String by NotNullVar() + final override var price: Long = 0L + final override var rarity: ItemRarity = ItemRarity.COMMON + final override var category: String? = null + final override var inventoryIcon: List? = null + final override var itemTags: List = listOf() + final override var learnBlueprintsOnPickup: List = listOf() + final override var maxStack: Long = 9999L + final override var eventCategory: String? = null + final override var consumeOnPickup: Boolean = false + final override var pickupQuestTemplates: List = listOf() + final override var scripts: List = listOf() + final override var tooltipKind: ItemTooltipKind = ItemTooltipKind.NORMAL + final override var twoHanded: Boolean = false + final override var radioMessagesOnPickup: List = listOf() + final override var fuelAmount: Long? = null + + var json: Map = mapOf() + + final override fun acceptJson(json: MutableMap) { + this.json = json + } + + open fun assemble(): IItemDefinition { + return ItemDefinition( + shortdescription = shortdescription, + description = description, + itemName = itemName, + price = price, + rarity = rarity, + category = category, + inventoryIcon = inventoryIcon, + itemTags = itemTags, + learnBlueprintsOnPickup = learnBlueprintsOnPickup, + maxStack = maxStack, + eventCategory = eventCategory, + consumeOnPickup = consumeOnPickup, + pickupQuestTemplates = pickupQuestTemplates, + scripts = scripts, + tooltipKind = tooltipKind, + twoHanded = twoHanded, + radioMessagesOnPickup = radioMessagesOnPickup, + fuelAmount = fuelAmount, + + json = enrollMap(json), + ) + } + + companion object { + val ADAPTER = BuilderAdapter.Builder(::ItemPrototype) + .also(::addFields) + .build() + + fun addFields(builder: BuilderAdapter.Builder) { + with(builder) { + auto(ItemPrototype::shortdescription) + auto(ItemPrototype::description) + auto(ItemPrototype::itemName) + auto(ItemPrototype::price) + auto(ItemPrototype::rarity) + auto(ItemPrototype::category) + autoNullableList(ItemPrototype::inventoryIcon) + autoList(ItemPrototype::itemTags) + autoList(ItemPrototype::learnBlueprintsOnPickup) + auto(ItemPrototype::maxStack) + auto(ItemPrototype::eventCategory) + auto(ItemPrototype::consumeOnPickup) + autoList(ItemPrototype::pickupQuestTemplates) + autoList(ItemPrototype::scripts) + auto(ItemPrototype::tooltipKind) + auto(ItemPrototype::twoHanded) + autoList(ItemPrototype::radioMessagesOnPickup) + auto(ItemPrototype::fuelAmount) + } + } + } +} diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/defs/item/ItemRarity.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/defs/item/ItemRarity.kt index 1703cf97..5090251c 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/defs/item/ItemRarity.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/defs/item/ItemRarity.kt @@ -3,7 +3,7 @@ package ru.dbotthepony.kstarbound.defs.item import com.google.gson.GsonBuilder import com.google.gson.TypeAdapter import com.google.gson.stream.JsonWriter -import ru.dbotthepony.kstarbound.io.json.CustomEnumTypeAdapter +import ru.dbotthepony.kstarbound.io.json.EnumAdapter import ru.dbotthepony.kstarbound.io.json.IStringSerializable import ru.dbotthepony.kstarbound.registerTypeAdapter @@ -21,12 +21,4 @@ enum class ItemRarity(val canonical: String) : IStringSerializable { override fun write(out: JsonWriter) { out.value(canonical) } - - companion object { - val ADAPTER: TypeAdapter = CustomEnumTypeAdapter(values()).nullSafe() - - fun registerGson(gsonBuilder: GsonBuilder) { - gsonBuilder.registerTypeAdapter(ADAPTER) - } - } } diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/defs/item/ItemTooltipKind.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/defs/item/ItemTooltipKind.kt new file mode 100644 index 00000000..a2df17fa --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/defs/item/ItemTooltipKind.kt @@ -0,0 +1,43 @@ +package ru.dbotthepony.kstarbound.defs.item + +enum class ItemTooltipKind { + /** + * Обычные предметы + */ + NORMAL, + + /** + * Улучшение для рюкзака + */ + BASE_AUGMENT, + + /** + * Рюкзаки + */ + BACK, + + /** + * Броня + */ + ARMOR, + + /** + * "Руки" меха + */ + MECH_ARM, + + /** + * "Ноги" меха + */ + MECH_LEGS, + + /** + * Ускорители меха + */ + MECH_BOOSTER, + + /** + * Тело меха + */ + MECH_BODY, +} diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/defs/projectile/Configurable.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/defs/projectile/Configurable.kt index c92f5a3f..2e769615 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/defs/projectile/Configurable.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/defs/projectile/Configurable.kt @@ -9,7 +9,7 @@ import ru.dbotthepony.kstarbound.Starbound import ru.dbotthepony.kstarbound.defs.* import ru.dbotthepony.kstarbound.defs.image.ImageReference import ru.dbotthepony.kstarbound.io.json.BuilderAdapter -import ru.dbotthepony.kstarbound.io.json.CustomEnumTypeAdapter +import ru.dbotthepony.kstarbound.io.json.EnumAdapter import ru.dbotthepony.kstarbound.registerTypeAdapter import ru.dbotthepony.kstarbound.util.NotNullVar import ru.dbotthepony.kvector.vector.Color @@ -80,7 +80,7 @@ class ConfigurableProjectile : RawPrototype() @@ -300,7 +300,7 @@ data class RenderTemplate( if (path[0] != '/') { // относительный путь - val readingFolder = Starbound.readingFolder ?: throw NullPointerException("Currently read folder is not specified") + val readingFolder = Starbound.assetFolder ?: throw NullPointerException("Currently read folder is not specified") path = "$readingFolder/$path" } diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/defs/world/SkyParameters.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/defs/world/SkyParameters.kt index 33ae1ea3..c73e5bb0 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/defs/world/SkyParameters.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/defs/world/SkyParameters.kt @@ -6,8 +6,8 @@ import com.google.gson.stream.JsonReader import com.google.gson.stream.JsonWriter import org.apache.logging.log4j.LogManager import ru.dbotthepony.kstarbound.io.ColorTypeAdapter -import ru.dbotthepony.kstarbound.io.json.CustomEnumTypeAdapter import ru.dbotthepony.kstarbound.io.json.BuilderAdapter +import ru.dbotthepony.kstarbound.io.json.EnumAdapter import ru.dbotthepony.kvector.vector.Color import ru.dbotthepony.kvector.vector.ndouble.Vector2d import kotlin.properties.ReadWriteProperty @@ -43,7 +43,7 @@ class SkyParameters { gsonBuilder.registerTypeAdapter(SkyParameters::class.java, ADAPTER) gsonBuilder.registerTypeAdapter(SkyColoringManifold::class.java, SkyColoringManifold.ADAPTER) gsonBuilder.registerTypeAdapter(SkyColoring::class.java, SkyColoring.ADAPTER) - gsonBuilder.registerTypeAdapter(SkyType::class.java, CustomEnumTypeAdapter(SkyType.values()).nullSafe()) + gsonBuilder.registerTypeAdapter(SkyType::class.java, EnumAdapter(SkyType::class)) SkySatellite.registerGson(gsonBuilder) } } diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/defs/world/dungeon/Configurable.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/defs/world/dungeon/Configurable.kt index 95fe1252..5d003eb2 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/defs/world/dungeon/Configurable.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/defs/world/dungeon/Configurable.kt @@ -3,8 +3,9 @@ package ru.dbotthepony.kstarbound.defs.world.dungeon import com.google.gson.GsonBuilder import ru.dbotthepony.kstarbound.defs.world.SkyParameters import ru.dbotthepony.kstarbound.defs.world.WorldProperties -import ru.dbotthepony.kstarbound.io.json.CustomEnumTypeAdapter import ru.dbotthepony.kstarbound.io.json.BuilderAdapter +import ru.dbotthepony.kstarbound.io.json.EnumAdapter +import ru.dbotthepony.kstarbound.registerTypeAdapter import kotlin.properties.Delegates class DungeonWorldDef { @@ -50,8 +51,8 @@ class DungeonWorldDef { fun registerGson(gsonBuilder: GsonBuilder) { gsonBuilder.registerTypeAdapter(DungeonWorldDef::class.java, ADAPTER) - gsonBuilder.registerTypeAdapter(BeamUpRule::class.java, CustomEnumTypeAdapter(BeamUpRule.values()).nullSafe()) - gsonBuilder.registerTypeAdapter(DungeonType::class.java, CustomEnumTypeAdapter(DungeonType.values()).nullSafe()) + gsonBuilder.registerTypeAdapter(EnumAdapter(BeamUpRule::class)) + gsonBuilder.registerTypeAdapter(EnumAdapter(DungeonType::class)) } } } diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/io/json/BuilderAdapter.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/io/json/BuilderAdapter.kt index dbcd4b52..6bd8aba9 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/io/json/BuilderAdapter.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/io/json/BuilderAdapter.kt @@ -98,7 +98,7 @@ class BuilderAdapter private constructor( reader = JsonTreeReader(obj) if (instance is INativeJsonHolder) { - instance.acceptJson(flattenJsonElement(obj.asJsonObject, Starbound.assetStringInterner::intern)) + instance.acceptJson(flattenJsonElement(obj.asJsonObject, Starbound.STRING_INTERNER::intern)) } else { instance.acceptJson(obj.asJsonObject) } @@ -121,12 +121,16 @@ class BuilderAdapter private constructor( val peek = reader.peek() if (!property.returnType.isMarkedNullable && peek == JsonToken.NULL) { - throw NullPointerException("Property ${property.name} of ${instance::class.qualifiedName} does not accept nulls") + throw NullPointerException("Property ${property.name} of ${instance::class.qualifiedName} does not accept nulls (JSON contains null)") } else if (peek == JsonToken.NULL) { property.set(instance, null) reader.nextNull() } else { val readValue = property.adapter.read(reader) + + if (!property.returnType.isMarkedNullable && readValue == null) + throw JsonSyntaxException("Property ${property.name} of ${instance::class.qualifiedName} does not accept nulls (Type provider returned null)") + property.set(instance, readValue) check(missing.remove(property)) } @@ -248,22 +252,39 @@ class BuilderAdapter private constructor( return this } + /** + * Автоматически определяет тип свойства и необходимый [TypeAdapter] + */ fun auto(property: KMutableProperty1, configurator: PropertyConfigurator.() -> Unit = {}): Builder { val returnType = property.returnType val classifier = returnType.classifier as? KClass<*> ?: throw ClassCastException("Unable to cast ${returnType.classifier} to KClass of property ${property.name}!") if (classifier.isSubclassOf(List::class)) { - throw IllegalArgumentException("${property.name} is a List, please use autoList() method instead") + throw IllegalArgumentException("${property.name} is a List, please use autoList() or directly specify type adapter method instead") } if (classifier.isSubclassOf(Map::class)) { - throw IllegalArgumentException("${property.name} is a Map, please use autoMap() method instead") + throw IllegalArgumentException("${property.name} is a Map, please use autoMap() or directly specify type adapter method instead") } @Suppress("unchecked_cast") // classifier.java не имеет обозначенного типа return add(property, LazyTypeProvider(classifier.java) as TypeAdapter, configurator) } + /** + * Автоматически создаёт [ListAdapter] для заданного свойства + */ + inline fun autoList(property: KMutableProperty1>, noinline configurator: PropertyConfigurator>.() -> Unit = {}): Builder { + return add(property, ListAdapter(V::class.java), configurator) + } + + /** + * Автоматически создаёт [ListAdapter] для заданного свойства, но в данном случае само свойство может принимать значение null + */ + inline fun autoNullableList(property: KMutableProperty1?>, noinline configurator: PropertyConfigurator?>.() -> Unit = {}): Builder { + return add(property, ListAdapter(V::class.java).nullSafe(), configurator) + } + fun ignoreKey(name: String): Builder { if (properties.any { it.property.name == name }) { throw IllegalArgumentException("Can not ignore key $name because we have property with this name!") diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/io/json/CustomEnumTypeAdapter.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/io/json/CustomEnumTypeAdapter.kt deleted file mode 100644 index 19d433a6..00000000 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/io/json/CustomEnumTypeAdapter.kt +++ /dev/null @@ -1,31 +0,0 @@ -package ru.dbotthepony.kstarbound.io.json - -import com.google.gson.TypeAdapter -import com.google.gson.stream.JsonReader -import com.google.gson.stream.JsonWriter - -interface IStringSerializable { - fun match(name: String): Boolean - fun write(out: JsonWriter) -} - -class CustomEnumTypeAdapter>(private val clazz: Array) : TypeAdapter() { - override fun write(out: JsonWriter, value: T) { - if (value is IStringSerializable) - value.write(out) - else - out.value(value.name) - } - - override fun read(`in`: JsonReader): T { - val str = `in`.nextString().uppercase() - - for (value in clazz) { - if (value is IStringSerializable && value.match(str) || value.name == str) { - return value - } - } - - throw IllegalArgumentException("${clazz[0]::class.qualifiedName} does not have value for $str") - } -} diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/io/json/EnumAdapter.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/io/json/EnumAdapter.kt index 752d47ce..70f479d8 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/io/json/EnumAdapter.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/io/json/EnumAdapter.kt @@ -1,40 +1,133 @@ package ru.dbotthepony.kstarbound.io.json +import com.google.common.collect.ImmutableList import com.google.common.collect.ImmutableMap +import com.google.common.collect.Streams import com.google.gson.JsonSyntaxException import com.google.gson.TypeAdapter import com.google.gson.stream.JsonReader +import com.google.gson.stream.JsonToken import com.google.gson.stream.JsonWriter import it.unimi.dsi.fastutil.objects.Object2ObjectArrayMap +import ru.dbotthepony.kstarbound.set +import java.util.Arrays +import java.util.stream.Stream +import kotlin.reflect.KClass +import kotlin.reflect.full.isSuperclassOf -class EnumAdapter>(private val enum: Class) : TypeAdapter() { - private val mapping: ImmutableMap = Object2ObjectArrayMap().let { - for (value in enum.enumConstants) { - it[value.name] = value - it[value.name.uppercase()] = value - it[value.name.lowercase()] = value +interface IStringSerializable { + fun match(name: String): Boolean + fun write(out: JsonWriter) +} + +@Suppress("FunctionName") +inline fun >EnumAdapter(values: Stream = Arrays.stream(T::class.java.enumConstants), default: T? = null): EnumAdapter { + return EnumAdapter(T::class, values, default) +} + +@Suppress("FunctionName") +inline fun >EnumAdapter(values: Iterator, default: T? = null): EnumAdapter { + return EnumAdapter(T::class, Streams.stream(values), default) +} + +@Suppress("FunctionName") +inline fun >EnumAdapter(values: Array, default: T? = null): EnumAdapter { + return EnumAdapter(T::class, Arrays.stream(values), default) +} + +@Suppress("FunctionName") +inline fun >EnumAdapter(values: Collection, default: T? = null): EnumAdapter { + return EnumAdapter(T::class, values.stream(), default) +} + +@Suppress("name_shadowing") +class EnumAdapter>(private val enum: KClass, values: Stream = Arrays.stream(enum.java.enumConstants), val default: T? = null) : TypeAdapter() { + constructor(clazz: Class, values: Stream = Arrays.stream(clazz.enumConstants), default: T? = null) : this(clazz.kotlin, values, default) + + constructor(clazz: Class, values: Iterator, default: T? = null) : this(clazz.kotlin, Streams.stream(values), default) + constructor(clazz: Class, values: Array, default: T? = null) : this(clazz.kotlin, Arrays.stream(values), default) + constructor(clazz: Class, values: Collection, default: T? = null) : this(clazz.kotlin, values.stream(), default) + + constructor(clazz: KClass, values: Iterator, default: T? = null) : this(clazz, Streams.stream(values), default) + constructor(clazz: KClass, values: Array, default: T? = null) : this(clazz, Arrays.stream(values), default) + constructor(clazz: KClass, values: Collection, default: T? = null) : this(clazz, values.stream(), default) + + private val values = values.collect(ImmutableList.toImmutableList()) + private val mapping: ImmutableMap + private val areCustom = IStringSerializable::class.isSuperclassOf(enum) + + init { + val builder = Object2ObjectArrayMap() + + for (value in this.values) { + builder[value.name] = value + builder[value.name.uppercase()] = value + builder[value.name.lowercase()] = value val spaced = value.name.replace('_', ' ') val stitched = value.name.replace("_", "") - it[spaced] = value - it[spaced.uppercase()] = value - it[spaced.lowercase()] = value + builder[spaced] = value + builder[spaced.uppercase()] = value + builder[spaced.lowercase()] = value - it[stitched] = value - it[stitched.uppercase()] = value - it[stitched.lowercase()] = value + builder[stitched] = value + builder[stitched.uppercase()] = value + builder[stitched.lowercase()] = value } - ImmutableMap.copyOf(it) + mapping = ImmutableMap.copyOf(builder) } - override fun write(out: JsonWriter, value: T) { - out.value(value.name) + override fun write(out: JsonWriter, value: T?) { + if (value == null) { + out.nullValue() + } else { + out.value(value.name) + } } - override fun read(`in`: JsonReader): T { + @Suppress("unchecked_cast") + override fun read(`in`: JsonReader): T? { + if (`in`.peek() == JsonToken.NULL) { + return null + } + val key = `in`.nextString() - return mapping[key] ?: throw JsonSyntaxException("Unable to match '$key' against ${enum.canonicalName}") + + if (areCustom) { + for (value in values) { + if ((value as IStringSerializable).match(key)) { + return value + } + } + } + + return mapping[key] ?: default + } + + /** + * Возвращает [EnumAdapter] с тем же типом и набором [T], которому запрещено возвращать или принимать null'ы + */ + fun neverNull(): TypeAdapter { + return object : TypeAdapter() { + override fun write(out: JsonWriter, value: T) { + return this@EnumAdapter.write(out, value) + } + + override fun read(`in`: JsonReader): T { + val key = `in`.nextString() + + if (areCustom) { + for (value in values) { + if ((value as IStringSerializable).match(key)) { + return value + } + } + } + + return mapping[key] ?: default ?: throw JsonSyntaxException("$key is not a valid ${enum.qualifiedName} value") + } + } } } 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 7506e7b9..371166f0 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/io/json/Ext.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/io/json/Ext.kt @@ -1,5 +1,6 @@ package ru.dbotthepony.kstarbound.io.json +import com.google.gson.JsonSyntaxException import com.google.gson.TypeAdapter import com.google.gson.stream.JsonReader import com.google.gson.stream.JsonToken @@ -56,3 +57,18 @@ fun TypeAdapter.ifString(reader: (String) -> T): TypeAdapter { } } } + +fun TypeAdapter.neverNull(): TypeAdapter { + return object : TypeAdapter() { + override fun write(out: JsonWriter, value: T) { + this@neverNull.write(out, value) + } + + override fun read(`in`: JsonReader): T { + val path = `in`.path + return this@neverNull.read(`in`) ?: throw JsonSyntaxException("Value was null near $path") + } + } +} + +fun TypeAdapter.allowNull(): TypeAdapter = nullSafe() diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/io/json/FactoryAdapter.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/io/json/FactoryAdapter.kt index 061ca52a..9375fb67 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/io/json/FactoryAdapter.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/io/json/FactoryAdapter.kt @@ -188,7 +188,7 @@ class FactoryAdapter private constructor( } reader = JsonTreeReader(readArray) - readValues[readValues.size - 1] = enrollList(flattenJsonElement(readArray) as List, Starbound.assetStringInterner::intern) + readValues[readValues.size - 1] = enrollList(flattenJsonElement(readArray) as List, Starbound.STRING_INTERNER::intern) } reader.beginArray() @@ -233,7 +233,7 @@ class FactoryAdapter private constructor( } reader = JsonTreeReader(readMap) - readValues[readValues.size - 1] = enrollMap(flattenJsonElement(readMap) as Map, Starbound.assetStringInterner::intern) + readValues[readValues.size - 1] = enrollMap(flattenJsonElement(readMap) as Map, Starbound.STRING_INTERNER::intern) } reader.beginObject() @@ -357,6 +357,12 @@ class FactoryAdapter private constructor( * Позволяет построить класс [FactoryAdapter] на основе заданных параметров */ class Builder(val clazz: KClass) { + constructor(clazz: KClass, vararg fields: KProperty1) : this(clazz) { + for (field in fields) { + auto(field) + } + } + private val types = ArrayList>() /** @@ -418,8 +424,8 @@ class FactoryAdapter private constructor( * * Список неизменяем (создаётся объект [ImmutableList]) */ - fun list(field: KProperty1?>, type: Class, transformer: (List?) -> List? = { it }): Builder { - types.add(PackedProperty(field, ListAdapter(type).nullSafe(), transformer = transformer)) + fun list(field: KProperty1?>, type: Class, transformer: (V) -> V = { it }): Builder { + types.add(PackedProperty(field, ListAdapter(type, transformer).nullSafe())) return this } @@ -428,8 +434,8 @@ class FactoryAdapter private constructor( * * Список неизменяем (создаётся объект [ImmutableList]) */ - inline fun autoList(field: KProperty1?>, noinline transformer: (List?) -> List? = { it }): Builder { - return add(field, ListAdapter(V::class.java).nullSafe()) + inline fun autoList(field: KProperty1?>, noinline transformer: (V) -> V = { it }): Builder { + return add(field, ListAdapter(V::class.java, transformer).nullSafe()) } /** diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/io/json/String2ObjectAdapter.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/io/json/String2ObjectAdapter.kt index def7f538..f662349e 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/io/json/String2ObjectAdapter.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/io/json/String2ObjectAdapter.kt @@ -31,7 +31,7 @@ class String2ObjectAdapter(val adapter: TypeAdapter, val valueTransformer: reader.beginObject() while (reader.peek() != JsonToken.END_OBJECT) { - builder.put(Starbound.assetStringInterner.intern(reader.nextName()), valueTransformer(adapter.read(reader))) + builder.put(Starbound.STRING_INTERNER.intern(reader.nextName()), valueTransformer(adapter.read(reader))) } reader.endObject() diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/world/entities/ItemEntity.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/world/entities/ItemEntity.kt index e337e4c3..021d4e15 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/world/entities/ItemEntity.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/world/entities/ItemEntity.kt @@ -5,10 +5,11 @@ import ru.dbotthepony.kbox2d.api.FixtureDef import ru.dbotthepony.kbox2d.api.Manifold import ru.dbotthepony.kbox2d.collision.shapes.PolygonShape import ru.dbotthepony.kbox2d.dynamics.contact.AbstractContact +import ru.dbotthepony.kstarbound.defs.item.IItemDefinition import ru.dbotthepony.kstarbound.defs.item.ItemDefinition import ru.dbotthepony.kstarbound.world.World -class ItemEntity(world: World<*, *>, val def: ItemDefinition) : Entity(world) { +class ItemEntity(world: World<*, *>, val def: IItemDefinition) : Entity(world) { override val movement = object : MovementController(this) { override fun beginContact(contact: AbstractContact) { // тут надо код подбора предмета игроком, если мы начинаем коллизию с окружностью подбора