diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/Starbound.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/Starbound.kt index a1100dfb..236e7f42 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/Starbound.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/Starbound.kt @@ -1,6 +1,5 @@ package ru.dbotthepony.kstarbound -import com.google.common.collect.ImmutableList import com.google.common.collect.Interner import com.google.common.collect.Interners import com.google.gson.* @@ -15,10 +14,8 @@ import ru.dbotthepony.kstarbound.api.NonExistingFile import ru.dbotthepony.kstarbound.api.PhysicalFile import ru.dbotthepony.kstarbound.api.explore import ru.dbotthepony.kstarbound.defs.* -import ru.dbotthepony.kstarbound.defs.image.AtlasConfiguration +import ru.dbotthepony.kstarbound.defs.image.ImageReference import ru.dbotthepony.kstarbound.defs.image.SpriteReference -import ru.dbotthepony.kstarbound.defs.item.ArmorItemPrototype -import ru.dbotthepony.kstarbound.defs.item.ArmorPieceType import ru.dbotthepony.kstarbound.defs.item.BackArmorItemPrototype import ru.dbotthepony.kstarbound.defs.item.ChestArmorItemPrototype import ru.dbotthepony.kstarbound.defs.item.CurrencyItemPrototype @@ -26,14 +23,9 @@ import ru.dbotthepony.kstarbound.defs.item.FlashlightPrototype import ru.dbotthepony.kstarbound.defs.item.HarvestingToolPrototype import ru.dbotthepony.kstarbound.defs.item.HeadArmorItemPrototype 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.ItemDefinition 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.LegsArmorItemPrototype -import ru.dbotthepony.kstarbound.defs.item.LeveledStatusEffect import ru.dbotthepony.kstarbound.defs.item.LiquidItemPrototype import ru.dbotthepony.kstarbound.defs.item.MaterialItemPrototype import ru.dbotthepony.kstarbound.defs.liquid.LiquidDefinition @@ -41,25 +33,16 @@ import ru.dbotthepony.kstarbound.defs.parallax.ParallaxPrototype import ru.dbotthepony.kstarbound.defs.player.PlayerDefinition import ru.dbotthepony.kstarbound.defs.projectile.* import ru.dbotthepony.kstarbound.defs.tile.MaterialModifier -import ru.dbotthepony.kstarbound.defs.tile.RenderParameters -import ru.dbotthepony.kstarbound.defs.tile.RenderTemplate import ru.dbotthepony.kstarbound.defs.tile.TileDefinition -import ru.dbotthepony.kstarbound.defs.world.SkyParameters -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.builder.EnumAdapter -import ru.dbotthepony.kstarbound.io.json.Vector2dTypeAdapter -import ru.dbotthepony.kstarbound.io.json.Vector2fTypeAdapter -import ru.dbotthepony.kstarbound.io.json.Vector2iTypeAdapter -import ru.dbotthepony.kstarbound.io.json.Vector4iTypeAdapter import ru.dbotthepony.kstarbound.io.json.builder.BuilderAdapter import ru.dbotthepony.kstarbound.io.json.builder.FactoryAdapter import ru.dbotthepony.kstarbound.io.json.builder.JsonImplementationTypeFactory import ru.dbotthepony.kstarbound.io.json.factory.ArrayListAdapterFactory import ru.dbotthepony.kstarbound.io.json.factory.ImmutableCollectionAdapterFactory import ru.dbotthepony.kstarbound.math.* +import ru.dbotthepony.kstarbound.util.AssetPathStack import ru.dbotthepony.kstarbound.util.WriteOnce import java.io.* import java.text.DateFormat @@ -80,36 +63,11 @@ fun String.sbIntern(): String = Starbound.STRING_INTERNER.intern(this) object Starbound { private val LOGGER = LogManager.getLogger() - /** - * Служит переменной для указания из какой папки происходит чтение asset'а в данном потоке - */ - var assetFolder by ThreadLocal() - private set + val STRING_INTERNER: Interner = Interners.newWeakInterner() + val pathStack = AssetPathStack(STRING_INTERNER) fun assetFolder(input: String): String { - val assetFolder = assetFolder - require(assetFolder != null) { "Not reading an asset on current thread" } - - if (input[0] == '/') - return input - - return STRING_INTERNER.intern("$assetFolder/$input") - } - - fun assetFolderNullable(input: String?): String? { - require(assetFolder != null) { "Not reading an asset on current thread" } - - if (input != null) - return assetFolder(input) - - return null - } - - fun readingFolderListTransformer(input: List?): List? { - if (input == null) - return null - - return input.stream().map { assetFolder(it) }.collect(ImmutableList.toImmutableList()) + return pathStack.remap(input) } private val tiles = Object2ObjectOpenHashMap() @@ -125,6 +83,7 @@ object Starbound { private val parallax = Object2ObjectOpenHashMap() private val functions = Object2ObjectOpenHashMap() private val species = Object2ObjectOpenHashMap() + private val statusEffects = Object2ObjectOpenHashMap() private val items = Object2ObjectOpenHashMap() @@ -139,8 +98,6 @@ object Starbound { val FUNCTION: Map = Collections.unmodifiableMap(functions) val ITEM: Map = Collections.unmodifiableMap(items) - val STRING_INTERNER: Interner = Interners.newWeakInterner() - val STRING_ADAPTER: TypeAdapter = object : TypeAdapter() { override fun write(out: JsonWriter, value: String?) { if (value == null) @@ -184,6 +141,20 @@ object Starbound { .also(::addStarboundJsonAdapters) + .registerTypeAdapterFactory(IItemDefinition.InventoryIcon.Factory(pathStack)) + .registerTypeAdapter(SpriteReference.Adapter(pathStack)) + .registerTypeAdapterFactory(IArmorItemDefinition.ArmorFrames.Factory(pathStack)) + .registerTypeAdapter(ImageReference.Adapter(pathStack)) + + .registerTypeAdapterFactory(AssetReferenceFactory(pathStack) { + val file = locate(it) + + if (file.exists && file.isFile) + file.reader() + else + null + }) + .registerTypeAdapterFactory(RegistryReferenceFactory() .add(tiles::get) .add(tileModifiers::get) @@ -193,6 +164,7 @@ object Starbound { .add(functions::get) .add(items::get) .add(species::get) + .add(statusEffects::get) ) .create() @@ -352,9 +324,9 @@ object Starbound { loadStage(callback, this::loadItemDefinitions, "item definitions") loadStage(callback, this::loadSpecies, "species") - assetFolder = "/" - playerDefinition = GSON.fromJson(locate("/player.config").reader(), PlayerDefinition::class.java) - assetFolder = null + pathStack.block("/") { + playerDefinition = GSON.fromJson(locate("/player.config").reader(), PlayerDefinition::class.java) + } initializing = false initialized = true @@ -381,16 +353,12 @@ object Starbound { } private fun loadTileMaterials(callback: (String) -> Unit) { - assetFolder = "/tiles/materials" - for (fs in fileSystems) { for (listedFile in fs.explore().filter { it.isFile }.filter { it.name.endsWith(".material") }) { try { callback("Loading $listedFile") - assetFolder = listedFile.computeDirectory() - val tileDef = GSON.fromJson(listedFile.reader(), TileDefinition::class.java) - + val tileDef = pathStack(listedFile.computeDirectory()) { GSON.fromJson(listedFile.reader(), TileDefinition::class.java) } check(tiles[tileDef.materialName] == null) { "Already has material with name ${tileDef.materialName} loaded!" } check(tilesByMaterialID[tileDef.materialId] == null) { "Already has material with ID ${tileDef.materialId} loaded!" } tilesByMaterialID[tileDef.materialId] = tileDef @@ -405,8 +373,6 @@ object Starbound { } } } - - assetFolder = null } private fun loadProjectiles(callback: (String) -> Unit) { @@ -415,8 +381,7 @@ object Starbound { try { callback("Loading $listedFile") - assetFolder = listedFile.computeDirectory() - val def = GSON.fromJson(listedFile.reader(), ConfigurableProjectile::class.java).assemble(listedFile.computeDirectory()) + val def = pathStack(listedFile.computeDirectory()) { GSON.fromJson(listedFile.reader(), ConfigurableProjectile::class.java).assemble(it) } check(projectiles[def.projectileName] == null) { "Already has projectile with ID ${def.projectileName} loaded!" } projectiles[def.projectileName] = def } catch(err: Throwable) { @@ -429,8 +394,6 @@ object Starbound { } } } - - assetFolder = null } private fun loadFunctions(callback: (String) -> Unit) { @@ -439,11 +402,10 @@ object Starbound { try { callback("Loading $listedFile") - assetFolder = listedFile.computeDirectory() val readObject = JsonParser.parseReader(listedFile.reader()) as JsonObject for (key in readObject.keySet()) { - val def = GSON.fromJson(readObject[key], JsonFunction::class.java) + val def = pathStack(listedFile.computeDirectory()) { GSON.fromJson(readObject[key], JsonFunction::class.java) } functions[key] = def } } catch(err: Throwable) { @@ -455,8 +417,6 @@ object Starbound { } } } - - assetFolder = null } private fun loadParallax(callback: (String) -> Unit) { @@ -464,9 +424,7 @@ object Starbound { for (listedFile in fs.explore().filter { it.isFile }.filter { it.name.endsWith(".parallax") }) { try { callback("Loading $listedFile") - - assetFolder = listedFile.computeDirectory() - val def = GSON.fromJson(listedFile.reader(), ParallaxPrototype::class.java) + val def = pathStack(listedFile.computeDirectory()) { GSON.fromJson(listedFile.reader(), ParallaxPrototype::class.java) } parallax[listedFile.name.substringBefore('.')] = def } catch(err: Throwable) { LOGGER.error("Loading parallax file $listedFile", err) @@ -477,21 +435,14 @@ object Starbound { } } } - - assetFolder = null } private fun loadMaterialModifiers(callback: (String) -> Unit) { - assetFolder = "/tiles/materials" - for (fs in fileSystems) { for (listedFile in fs.explore().filter { it.isFile }.filter { it.name.endsWith(".matmod") }) { try { callback("Loading $listedFile") - - assetFolder = listedFile.computeDirectory() - val tileDef = GSON.fromJson(listedFile.reader(), MaterialModifier::class.java) - + val tileDef = pathStack(listedFile.computeDirectory()) { GSON.fromJson(listedFile.reader(), MaterialModifier::class.java) } check(tileModifiers[tileDef.modName] == null) { "Already has material with name ${tileDef.modName} loaded!" } check(tileModifiersByID[tileDef.modId] == null) { "Already has material with ID ${tileDef.modId} loaded!" } tileModifiersByID[tileDef.modId] = tileDef @@ -506,8 +457,6 @@ object Starbound { } } } - - assetFolder = null } private fun loadLiquidDefinitions(callback: (String) -> Unit) { @@ -515,10 +464,7 @@ object Starbound { for (listedFile in fs.explore().filter { it.isFile }.filter { it.name.endsWith(".liquid") }) { try { callback("Loading $listedFile") - - assetFolder = listedFile.computeDirectory() - val liquidDef = GSON.fromJson(listedFile.reader(), LiquidDefinition::class.java) - + val liquidDef = pathStack(listedFile.computeDirectory()) { GSON.fromJson(listedFile.reader(), LiquidDefinition::class.java) } check(liquid.put(liquidDef.name, liquidDef) == null) { "Already has liquid with name ${liquidDef.name} loaded!" } check(liquidByID.put(liquidDef.liquidId, liquidDef) == null) { "Already has liquid with ID ${liquidDef.liquidId} loaded!" } } catch (err: Throwable) { @@ -531,8 +477,6 @@ object Starbound { } } } - - assetFolder = null } private fun loadSpecies(callback: (String) -> Unit) { @@ -540,9 +484,7 @@ object Starbound { for (listedFile in fs.explore().filter { it.isFile }.filter { it.name.endsWith(".species") }) { try { callback("Loading $listedFile") - - assetFolder = listedFile.computeDirectory() - val def = GSON.fromJson(listedFile.reader(), Species::class.java) + val def = pathStack(listedFile.computeDirectory()) { GSON.fromJson(listedFile.reader(), Species::class.java) } check(species.put(def.kind, def) == null) { "Already has liquid with name ${def.kind} loaded!" } } catch (err: Throwable) { LOGGER.error("Loading species definition file $listedFile", err) @@ -553,8 +495,6 @@ object Starbound { } } } - - assetFolder = null } private fun loadItemDefinitions(callback: (String) -> Unit) { @@ -575,10 +515,7 @@ object Starbound { for (listedFile in fs.explore().filter { it.isFile }.filter { f -> files.keys.any { f.name.endsWith(it) } }) { try { callback("Loading $listedFile") - - assetFolder = listedFile.computeDirectory() - - val def: ItemPrototype = GSON.fromJson(listedFile.reader(), files.entries.first { listedFile.name.endsWith(it.key) }.value) + val def: ItemPrototype = pathStack(listedFile.computeDirectory()) { GSON.fromJson(listedFile.reader(), files.entries.first { listedFile.name.endsWith(it.key) }.value) } 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) @@ -589,7 +526,5 @@ object Starbound { } } } - - assetFolder = null } } diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/StarboundJsonAdapters.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/StarboundJsonAdapters.kt index 56088941..77ca8c92 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/StarboundJsonAdapters.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/StarboundJsonAdapters.kt @@ -75,17 +75,12 @@ fun addStarboundJsonAdapters(builder: GsonBuilder) { registerTypeAdapterFactory(ParallaxPrototypeLayer.ADAPTER) registerTypeAdapter(ParallaxPrototypeLayer.LAYER_PARALLAX_ADAPTER) - // Предметы - registerTypeAdapterFactory(IItemDefinition.InventoryIcon.ADAPTER) - registerTypeAdapterFactory(IArmorItemDefinition.ArmorFrames.ADAPTER) - // Функции registerTypeAdapter(JsonFunction.CONSTRAINT_ADAPTER) registerTypeAdapter(JsonFunction.INTERPOLATION_ADAPTER) registerTypeAdapter(JsonFunction.Companion) // Общее - registerTypeAdapter(SpriteReference.Companion) registerTypeAdapterFactory(AtlasConfiguration.Companion) registerTypeAdapterFactory(LeveledStatusEffect.ADAPTER) diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/client/render/TileRenderer.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/client/render/TileRenderer.kt index 19944e58..1d716ac7 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/client/render/TileRenderer.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/client/render/TileRenderer.kt @@ -183,7 +183,7 @@ private class ModifierEqualityTester(val definition: MaterialModifier) : Equalit } class TileRenderer(val state: GLStateTracker, val def: IRenderableTile) { - val texture = state.loadNamedTexture(def.renderParameters.absoluteTexturePath).also { + val texture = state.loadNamedTexture(def.renderParameters.texture.image).also { it.textureMagFilter = GL_NEAREST } @@ -293,7 +293,7 @@ class TileRenderer(val state: GLStateTracker, val def: IRenderableTile) { fun tesselate(self: ITileState, getter: ITileChunk, layers: TileLayerList, pos: Vector2i, background: Boolean = false, isModifier: Boolean = false) { // если у нас нет renderTemplate // то мы просто не можем его отрисовать - val template = def.renderTemplate + val template = def.renderTemplate.value ?: return val vertexBuilder = layers.computeIfAbsent(if (background) bakedBackgroundProgramState else bakedProgramState, def.renderParameters.zLevel, ::vertexTextureBuilder) diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/defs/AssetReference.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/defs/AssetReference.kt new file mode 100644 index 00000000..b697f27e --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/defs/AssetReference.kt @@ -0,0 +1,81 @@ +package ru.dbotthepony.kstarbound.defs + +import com.google.gson.Gson +import com.google.gson.TypeAdapter +import com.google.gson.TypeAdapterFactory +import com.google.gson.reflect.TypeToken +import com.google.gson.stream.JsonReader +import com.google.gson.stream.JsonToken +import com.google.gson.stream.JsonWriter +import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet +import ru.dbotthepony.kstarbound.util.AssetPathStack +import java.io.Reader +import java.lang.reflect.ParameterizedType +import java.util.* +import java.util.concurrent.ConcurrentHashMap + +/** + * Данный [AssetReferenceFactory] реализует возможности чтения данных по "ссылке" на файл. + * + * Созданный [TypeAdapter] имеет встроенный кеш. + */ +class AssetReferenceFactory(val remapper: AssetPathStack, val reader: (String) -> Reader?) : TypeAdapterFactory { + override fun create(gson: Gson, type: TypeToken): TypeAdapter? { + if (type.rawType == AssetReference::class.java) { + val param = type.type as? ParameterizedType ?: return null + + return object : TypeAdapter>() { + private val cache = ConcurrentHashMap() + private val adapter = gson.getAdapter(TypeToken.get(param.actualTypeArguments[0])) as TypeAdapter + private val strings = gson.getAdapter(String::class.java) + private val missing = Collections.synchronizedSet(ObjectOpenHashSet()) + + override fun write(out: JsonWriter, value: AssetReference?) { + if (value == null) + out.nullValue() + else + out.value(value.fullPath) + } + + override fun read(`in`: JsonReader): AssetReference? { + if (`in`.peek() == JsonToken.NULL) { + return null + } else if (`in`.peek() == JsonToken.STRING) { + val path = strings.read(`in`)!! + val fullPath = remapper.remap(path) + val get = cache[fullPath] + + if (get != null) + return AssetReference(path, fullPath, get) + + if (fullPath in missing) + return null + + val reader = reader.invoke(fullPath) + + if (reader == null) { + missing.add(fullPath) + return AssetReference(path, fullPath, null) + } + + val value = remapper(fullPath) { + adapter.read(JsonReader(reader).also { + it.isLenient = true + }) ?: return AssetReference(path, fullPath, null) + } + + cache[fullPath] = value + return AssetReference(path, fullPath, value) + } else { + val value = adapter.read(`in`) ?: return null + return AssetReference(null, null, value) + } + } + } as TypeAdapter + } + + return null + } +} + +data class AssetReference(val path: String?, val fullPath: String?, val value: V?) diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/defs/Species.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/defs/Species.kt index e501c41f..ab2a7242 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/defs/Species.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/defs/Species.kt @@ -25,6 +25,7 @@ data class Species( val undyColor: ImmutableList, val hairColor: ImmutableList, val genders: ImmutableList, + val statusEffects: ImmutableSet> = ImmutableSet.of(), ) { @JsonFactory data class Tooltip(val title: String, val subTitle: String, val description: String) diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/defs/StatusEffectDefinition.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/defs/StatusEffectDefinition.kt new file mode 100644 index 00000000..255a6696 --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/defs/StatusEffectDefinition.kt @@ -0,0 +1,13 @@ +package ru.dbotthepony.kstarbound.defs + +import com.google.common.collect.ImmutableList +import ru.dbotthepony.kstarbound.defs.animation.AnimationDefinition +import ru.dbotthepony.kstarbound.io.json.builder.JsonFactory + +@JsonFactory +data class StatusEffectDefinition( + val name: String, + val defaultDuration: Double, + val scripts: ImmutableList, + val animationConfig: AssetReference, +) diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/defs/animation/AnimationDefinition.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/defs/animation/AnimationDefinition.kt index de0af09c..9191928e 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/defs/animation/AnimationDefinition.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/defs/animation/AnimationDefinition.kt @@ -6,7 +6,7 @@ import ru.dbotthepony.kstarbound.defs.image.SpriteReference import ru.dbotthepony.kstarbound.io.json.builder.JsonFactory import ru.dbotthepony.kvector.vector.ndouble.Vector2d -@JsonFactory(asReference = true) +@JsonFactory data class AnimationDefinition( val animatedParts: AnimatedParts, val sounds: ImmutableMap = ImmutableMap.of(), 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 6fd2079c..8caf2ad2 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/defs/image/ImageReference.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/defs/image/ImageReference.kt @@ -6,6 +6,7 @@ import com.google.gson.stream.JsonReader import com.google.gson.stream.JsonToken import com.google.gson.stream.JsonWriter import ru.dbotthepony.kstarbound.Starbound +import ru.dbotthepony.kstarbound.util.AssetPathStack /** * Хранит данные (пару) вида "/example/animated.png" у которого, вероятнее всего, есть "/example/animated.frames" @@ -23,23 +24,28 @@ data class ImageReference( */ constructor(image: String) : this(image, AtlasConfiguration.get(image)) - companion object : TypeAdapter() { - override fun write(out: JsonWriter, value: ImageReference) { - out.value(value.image) + class Adapter(val remapper: AssetPathStack) : TypeAdapter() { + override fun write(out: JsonWriter, value: ImageReference?) { + if (value == null) + out.nullValue() + else + out.value(value.image) } - override fun read(`in`: JsonReader): ImageReference { - if (`in`.peek() == JsonToken.STRING) { - val image = Starbound.assetFolder(`in`.nextString()) + override fun read(`in`: JsonReader): ImageReference? { + if (`in`.peek() == JsonToken.NULL) + return null - if (image.contains(':')) { + if (`in`.peek() == JsonToken.STRING) { + val image = remapper.remap(`in`.nextString()) + + if (image.contains(':')) throw JsonSyntaxException("Expected atlas/image reference, but got sprite reference: $image") - } return ImageReference(image, AtlasConfiguration.get(image)) } - throw JsonSyntaxException("Expected atlas/image reference, but got: ${`in`.peek()}") + throw JsonSyntaxException("Expected atlas/image reference, but got: ${`in`.peek()} near ${`in`.path}") } } } 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 3f2a7090..93cda66b 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/defs/image/SpriteReference.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/defs/image/SpriteReference.kt @@ -4,6 +4,7 @@ import com.google.gson.TypeAdapter import com.google.gson.stream.JsonReader import com.google.gson.stream.JsonWriter import ru.dbotthepony.kstarbound.Starbound +import ru.dbotthepony.kstarbound.util.AssetPathStack import ru.dbotthepony.kstarbound.util.SBPattern /** @@ -21,7 +22,17 @@ data class SpriteReference( return atlas[resolved] ?: atlas.any() } - companion object : TypeAdapter() { + class Adapter(val remapper: AssetPathStack) : TypeAdapter() { + override fun write(out: JsonWriter, value: SpriteReference) { + out.value(value.image + ":" + value.sprite.raw) + } + + override fun read(`in`: JsonReader): SpriteReference { + return parse(remapper.remap(`in`.nextString())) + } + } + + companion object { fun parse(input: String): SpriteReference { val grid = AtlasConfiguration.get(input.substringBefore(':')) @@ -31,13 +42,5 @@ data class SpriteReference( else -> throw IllegalArgumentException("Invalid sprite reference: $input") } } - - override fun write(out: JsonWriter, value: SpriteReference) { - out.value(value.image + ":" + value.sprite.raw) - } - - override fun read(`in`: JsonReader): SpriteReference { - return parse(Starbound.assetFolder(`in`.nextString())) - } } } diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/defs/item/IArmorItemDefinition.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/defs/item/IArmorItemDefinition.kt index 9baee409..435dcd9a 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/defs/item/IArmorItemDefinition.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/defs/item/IArmorItemDefinition.kt @@ -1,9 +1,17 @@ package ru.dbotthepony.kstarbound.defs.item -import ru.dbotthepony.kstarbound.Starbound +import com.google.gson.Gson +import com.google.gson.TypeAdapter +import com.google.gson.TypeAdapterFactory +import com.google.gson.reflect.TypeToken +import com.google.gson.stream.JsonReader +import com.google.gson.stream.JsonToken +import com.google.gson.stream.JsonWriter +import ru.dbotthepony.kstarbound.defs.image.AtlasConfiguration +import ru.dbotthepony.kstarbound.defs.image.ImageReference import ru.dbotthepony.kstarbound.io.json.builder.FactoryAdapter import ru.dbotthepony.kstarbound.io.json.builder.JsonImplementation -import ru.dbotthepony.kstarbound.io.json.ifString +import ru.dbotthepony.kstarbound.util.AssetPathStack interface IArmorItemDefinition : ILeveledItemDefinition, IScriptableItemDefinition { /** @@ -28,23 +36,50 @@ interface IArmorItemDefinition : ILeveledItemDefinition, IScriptableItemDefiniti @JsonImplementation(ArmorFrames::class) interface IArmorFrames { - val body: String - val backSleeve: String? - val frontSleeve: String? + val body: ImageReference + val backSleeve: ImageReference? + val frontSleeve: ImageReference? } data class ArmorFrames( - override val body: String, - override val backSleeve: String?, - override val frontSleeve: String?, + override val body: ImageReference, + override val backSleeve: ImageReference? = null, + override val frontSleeve: ImageReference? = null, ) : IArmorFrames { - companion object { - val ADAPTER = FactoryAdapter.Builder( - ArmorFrames::class, - ArmorFrames::body, - ArmorFrames::backSleeve, - ArmorFrames::frontSleeve, - ).ifString { ArmorFrames(Starbound.assetFolder(it), null, null) } + class Factory(private val remapper: AssetPathStack) : TypeAdapterFactory { + override fun create(gson: Gson, type: TypeToken): TypeAdapter? { + if (type.rawType == ArmorFrames::class.java) { + return object : TypeAdapter() { + private val adapter = FactoryAdapter.Builder( + ArmorFrames::class, + ArmorFrames::body, + ArmorFrames::backSleeve, + ArmorFrames::frontSleeve, + ).build(gson) + + override fun write(out: JsonWriter, value: ArmorFrames?) { + if (value == null) + out.nullValue() + else + adapter.write(out, value) + } + + override fun read(`in`: JsonReader): ArmorFrames? { + if (`in`.peek() == JsonToken.NULL) + return null + + if (`in`.peek() == JsonToken.STRING) { + val path = remapper.remap(`in`.nextString()) + return ArmorFrames(ImageReference(path, AtlasConfiguration.get(path)), null, null) + } + + return adapter.read(`in`) + } + } as TypeAdapter + } + + return null + } } } } diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/defs/item/IItemDefinition.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/defs/item/IItemDefinition.kt index d26709ed..bd8264ed 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/defs/item/IItemDefinition.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/defs/item/IItemDefinition.kt @@ -1,11 +1,19 @@ package ru.dbotthepony.kstarbound.defs.item +import com.google.gson.Gson +import com.google.gson.TypeAdapter +import com.google.gson.TypeAdapterFactory +import com.google.gson.reflect.TypeToken +import com.google.gson.stream.JsonReader +import com.google.gson.stream.JsonToken +import com.google.gson.stream.JsonWriter import ru.dbotthepony.kstarbound.Starbound import ru.dbotthepony.kstarbound.defs.IThingWithDescription import ru.dbotthepony.kstarbound.defs.image.SpriteReference import ru.dbotthepony.kstarbound.io.json.builder.FactoryAdapter import ru.dbotthepony.kstarbound.io.json.builder.JsonImplementation import ru.dbotthepony.kstarbound.io.json.ifString +import ru.dbotthepony.kstarbound.util.AssetPathStack interface IItemDefinition : IThingWithDescription { /** @@ -45,8 +53,34 @@ interface IItemDefinition : IThingWithDescription { data class InventoryIcon( override val image: SpriteReference ) : IInventoryIcon { - companion object { - val ADAPTER = FactoryAdapter.Builder(InventoryIcon::class, InventoryIcon::image).ifString { InventoryIcon(SpriteReference.parse(Starbound.assetFolder(it))) } + class Factory(val remapper: AssetPathStack) : TypeAdapterFactory { + override fun create(gson: Gson, type: TypeToken): TypeAdapter? { + if (type.rawType == InventoryIcon::class.java) { + return object : TypeAdapter() { + private val adapter = FactoryAdapter.Builder(InventoryIcon::class, InventoryIcon::image).build(gson) + + override fun write(out: JsonWriter, value: InventoryIcon?) { + if (value == null) + out.nullValue() + else + adapter.write(out, value) + } + + override fun read(`in`: JsonReader): InventoryIcon? { + if (`in`.peek() == JsonToken.NULL) + return null + + if (`in`.peek() == JsonToken.STRING) { + return InventoryIcon(SpriteReference.parse(remapper.remap(`in`.nextString()))) + } + + return adapter.read(`in`) + } + } as TypeAdapter + } + + return null + } } } diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/defs/player/PlayerDefinition.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/defs/player/PlayerDefinition.kt index 91f747ab..6f7d16e0 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/defs/player/PlayerDefinition.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/defs/player/PlayerDefinition.kt @@ -4,6 +4,7 @@ import com.google.common.collect.ImmutableList import com.google.common.collect.ImmutableMap import com.google.common.collect.ImmutableSet import com.google.gson.JsonObject +import ru.dbotthepony.kstarbound.defs.AssetReference import ru.dbotthepony.kstarbound.defs.RegistryReference import ru.dbotthepony.kstarbound.defs.Species import ru.dbotthepony.kstarbound.util.SBPattern @@ -58,7 +59,7 @@ data class PlayerDefinition( val splashConfig: SplashConfig, - val effectsAnimator: AnimationDefinition, + val effectsAnimator: AssetReference, val teleportInTime: Double, val teleportOutTime: Double, diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/defs/tile/IRenderableTile.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/defs/tile/IRenderableTile.kt index efe28c76..d47e3cd8 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/defs/tile/IRenderableTile.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/defs/tile/IRenderableTile.kt @@ -1,6 +1,8 @@ package ru.dbotthepony.kstarbound.defs.tile +import ru.dbotthepony.kstarbound.defs.AssetReference + sealed interface IRenderableTile { - val renderTemplate: RenderTemplate + val renderTemplate: AssetReference val renderParameters: RenderParameters } diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/defs/tile/MaterialModifier.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/defs/tile/MaterialModifier.kt index 316758d9..117a33f5 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/defs/tile/MaterialModifier.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/defs/tile/MaterialModifier.kt @@ -2,6 +2,7 @@ package ru.dbotthepony.kstarbound.defs.tile import com.google.common.collect.ImmutableList import com.google.gson.GsonBuilder +import ru.dbotthepony.kstarbound.defs.AssetReference import ru.dbotthepony.kstarbound.io.json.builder.FactoryAdapter import ru.dbotthepony.kstarbound.io.json.builder.JsonFactory @@ -17,7 +18,7 @@ data class MaterialModifier( val grass: Boolean = false, val miningSounds: ImmutableList = ImmutableList.of(), val miningParticle: String? = null, - override val renderTemplate: RenderTemplate, + override val renderTemplate: AssetReference, override val renderParameters: RenderParameters ) : IRenderableTile { init { diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/defs/tile/RenderParameters.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/defs/tile/RenderParameters.kt index ca0b53f5..454f7f7f 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/defs/tile/RenderParameters.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/defs/tile/RenderParameters.kt @@ -2,6 +2,7 @@ package ru.dbotthepony.kstarbound.defs.tile import com.google.gson.GsonBuilder import ru.dbotthepony.kstarbound.Starbound +import ru.dbotthepony.kstarbound.defs.image.ImageReference import ru.dbotthepony.kstarbound.io.json.builder.FactoryAdapter import ru.dbotthepony.kstarbound.io.json.builder.JsonFactory @@ -9,22 +10,10 @@ const val TILE_COLOR_VARIANTS = 9 @JsonFactory data class RenderParameters( - val texture: String, + val texture: ImageReference, val variants: Int = 0, val multiColored: Boolean = false, val occludesBelow: Boolean = false, val lightTransparent: Boolean = false, val zLevel: Int, -) { - val absoluteTexturePath: String - - init { - val dir = Starbound.assetFolder - - if (dir == null || texture[0] == '/') { - absoluteTexturePath = texture - } else { - absoluteTexturePath = "$dir/$texture" - } - } -} +) diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/defs/tile/RenderTemplate.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/defs/tile/RenderTemplate.kt index b0ff6004..f47cabdd 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/defs/tile/RenderTemplate.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/defs/tile/RenderTemplate.kt @@ -2,26 +2,13 @@ package ru.dbotthepony.kstarbound.defs.tile import com.google.common.collect.ImmutableList import com.google.common.collect.ImmutableMap -import com.google.gson.GsonBuilder -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.ObjectArraySet import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet import org.apache.logging.log4j.LogManager -import ru.dbotthepony.kstarbound.Starbound -import ru.dbotthepony.kstarbound.io.json.builder.EnumAdapter -import ru.dbotthepony.kstarbound.io.json.builder.FactoryAdapter import ru.dbotthepony.kstarbound.io.json.builder.JsonFactory -import ru.dbotthepony.kstarbound.io.json.util.asReference -import ru.dbotthepony.kstarbound.registerTypeAdapter import ru.dbotthepony.kstarbound.util.WriteOnce import ru.dbotthepony.kstarbound.world.ITileGetter import ru.dbotthepony.kstarbound.world.ITileState import ru.dbotthepony.kvector.vector.nint.Vector2i -import java.util.concurrent.ConcurrentHashMap @JsonFactory data class RenderPiece( @@ -202,7 +189,7 @@ data class RenderMatchList( } } -@JsonFactory(asReference = true) +@JsonFactory data class RenderTemplate( val pieces: ImmutableMap, val representativePiece: String, diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/defs/tile/TileDefinition.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/defs/tile/TileDefinition.kt index c8285e0f..b2c2d32f 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/defs/tile/TileDefinition.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/defs/tile/TileDefinition.kt @@ -1,6 +1,7 @@ package ru.dbotthepony.kstarbound.defs.tile import com.google.gson.GsonBuilder +import ru.dbotthepony.kstarbound.defs.AssetReference import ru.dbotthepony.kstarbound.io.json.builder.FactoryAdapter import ru.dbotthepony.kstarbound.io.json.builder.JsonFactory import ru.dbotthepony.kstarbound.registerTypeAdapter @@ -22,6 +23,6 @@ data class TileDefinition( val health: Double = 0.0, val category: String, - override val renderTemplate: RenderTemplate, + override val renderTemplate: AssetReference, override val renderParameters: RenderParameters, ) : IRenderableTile diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/io/json/builder/Annotations.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/io/json/builder/Annotations.kt index 120da896..42beb0db 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/io/json/builder/Annotations.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/io/json/builder/Annotations.kt @@ -1,6 +1,5 @@ package ru.dbotthepony.kstarbound.io.json.builder -import ru.dbotthepony.kstarbound.io.json.util.ReferenceAdapter import com.google.gson.Gson import com.google.gson.TypeAdapter import com.google.gson.TypeAdapterFactory @@ -42,11 +41,6 @@ annotation class JsonBuilder( * Включать ли свойства родительского класса в данный [BuilderAdapter] */ val includeSuperclassProperties: Boolean = true, - - /** - * Оборачивает созданный [BuilderAdapter] в [ReferenceAdapter] - */ - val asReference: Boolean = false, ) val JsonBuilder.realLogMisses get() = logMisses.toBool() @@ -102,11 +96,6 @@ annotation class JsonFactory( * @see FactoryAdapter.Builder.inputAsMap */ val asList: Boolean = false, - - /** - * Оборачивает созданный [FactoryAdapter] в [ReferenceAdapter] - */ - val asReference: Boolean = false, ) /** diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/io/json/builder/BuilderAdapter.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/io/json/builder/BuilderAdapter.kt index 13421634..8613bb8f 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/io/json/builder/BuilderAdapter.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/io/json/builder/BuilderAdapter.kt @@ -20,11 +20,9 @@ import it.unimi.dsi.fastutil.objects.Reference2ObjectOpenHashMap import org.apache.logging.log4j.LogManager import ru.dbotthepony.kstarbound.Starbound import ru.dbotthepony.kstarbound.defs.util.flattenJsonElement -import ru.dbotthepony.kstarbound.io.json.util.asReference +import ru.dbotthepony.kstarbound.io.json.builder.BuilderAdapter.Builder import ru.dbotthepony.kstarbound.util.NotNullVar -import java.util.concurrent.ConcurrentHashMap import kotlin.properties.Delegates -import kotlin.reflect.KCallable import kotlin.reflect.KClass import kotlin.reflect.KMutableProperty1 import kotlin.reflect.full.declaredMembers @@ -423,10 +421,6 @@ class BuilderAdapter private constructor( } } - if (bconfig.asReference) { - return builder.build(gson).asReference() - } - return builder.build(gson) } 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 7246fb4b..19f3c41f 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 @@ -24,20 +24,13 @@ 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.ifString -import ru.dbotthepony.kstarbound.io.json.util.LazyTypeProvider -import ru.dbotthepony.kstarbound.io.json.util.ListAdapter -import ru.dbotthepony.kstarbound.io.json.util.MapAdapter -import ru.dbotthepony.kstarbound.io.json.util.String2ObjectAdapter -import ru.dbotthepony.kstarbound.io.json.util.asReference import ru.dbotthepony.kstarbound.setValue import java.lang.reflect.Constructor import kotlin.jvm.internal.DefaultConstructorMarker import kotlin.properties.Delegates import kotlin.reflect.* import kotlin.reflect.full.declaredMembers -import kotlin.reflect.full.hasAnnotation import kotlin.reflect.full.isSubclassOf -import kotlin.reflect.full.isSubtypeOf import kotlin.reflect.full.isSupertypeOf import kotlin.reflect.full.primaryConstructor @@ -572,10 +565,6 @@ class FactoryAdapter private constructor( } } - if (bconfig.asReference) { - return builder.build(gson).asReference() - } - return builder.build(gson) } diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/io/json/util/ReferenceAdapter.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/io/json/util/ReferenceAdapter.kt deleted file mode 100644 index ffec14d5..00000000 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/io/json/util/ReferenceAdapter.kt +++ /dev/null @@ -1,79 +0,0 @@ -package ru.dbotthepony.kstarbound.io.json.util - -import com.google.gson.Gson -import com.google.gson.TypeAdapter -import com.google.gson.TypeAdapterFactory -import com.google.gson.reflect.TypeToken -import com.google.gson.stream.JsonReader -import com.google.gson.stream.JsonToken -import com.google.gson.stream.JsonWriter -import ru.dbotthepony.kstarbound.Starbound -import java.util.concurrent.ConcurrentHashMap - -/** - * Данный [TypeAdapter] реализует возможности чтения данных по "ссылке" на файл. - * - * К примеру, если в оригинальной структуре может (или обычно) встречаться такое: - * - * ```json - * { - * "thing": { ... }, - * } - * ``` - * - * и такое: - * - * ```json - * { - * "thing": "/path/to/thing.config" - * } - * ``` - * - * То второй вариант будет прочитан из указанного пути и закеширован внутри [ReferenceAdapter] - */ -class ReferenceAdapter(val parent: TypeAdapter) : TypeAdapter() { - private val cache = ConcurrentHashMap() - - override fun write(out: JsonWriter, value: T) { - parent.write(out, value) - } - - override fun read(reader: JsonReader): T { - if (reader.peek() != JsonToken.STRING) { - return parent.read(reader) - } - - var path = reader.nextString() - - if (path[0] != '/') { - // относительный путь - - val readingFolder = Starbound.assetFolder ?: throw NullPointerException("Currently read folder is not specified") - path = "$readingFolder/$path" - } - - return cache.computeIfAbsent(path) { - return@computeIfAbsent parent.read(JsonReader(Starbound.locate(it).reader()).also { it.isLenient = true }) - } - } -} - -class ReferenceAdapterFactory(val parent: TypeAdapterFactory) : TypeAdapterFactory { - override fun create(gson: Gson, type: TypeToken): TypeAdapter? { - val candidate = parent.create(gson, type) - - if (candidate != null) { - return ReferenceAdapter(candidate) - } - - return null - } -} - -fun TypeAdapter.asReference(): TypeAdapter { - return ReferenceAdapter(this) -} - -fun TypeAdapterFactory.asReference(): TypeAdapterFactory { - return ReferenceAdapterFactory(this) -} diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/util/AssetPathStack.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/util/AssetPathStack.kt new file mode 100644 index 00000000..82f39883 --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/util/AssetPathStack.kt @@ -0,0 +1,46 @@ +package ru.dbotthepony.kstarbound.util + +import com.google.common.collect.Interner +import kotlin.concurrent.getOrSet + +class AssetPathStack(private val interner: Interner? = null) { + private val _stack = ThreadLocal>() + private val stack: ArrayDeque get() = _stack.getOrSet { ArrayDeque() } + + fun push(value: String) { + stack.addLast(value) + } + + fun pushFilename(value: String) { + push(value.substringBefore(':').substringBeforeLast('/')) + } + + fun last() = stack.lastOrNull() + fun pop() = stack.removeLast() + + inline fun block(path: String, block: (String) -> T): T { + try { + push(path) + return block.invoke(path) + } finally { + pop() + } + } + + inline operator fun invoke(path: String, block: (String) -> T) = block(path, block) + + private fun remap(a: String, b: String): String { + if (b[0] == '/') + return b + + return interner?.intern("$a/$b") ?: "$a/$b" + } + + fun remap(path: String): String { + return remap(checkNotNull(last()) { "Not reading an asset on current thread" }, path) + } + + fun remapSafe(path: String): String? { + return remap(last() ?: return null, path) + } +}