From 7c318966d5a9b129d0e3f13b60cd1cf387ed4d98 Mon Sep 17 00:00:00 2001 From: DBotThePony Date: Fri, 26 Aug 2022 22:30:32 +0700 Subject: [PATCH] Move tile definition and render template to KConcreteTypeAdapter --- .../kotlin/ru/dbotthepony/kstarbound/Main.kt | 80 --- .../ru/dbotthepony/kstarbound/Starbound.kt | 18 +- .../kstarbound/client/render/TileRenderer.kt | 32 +- .../kstarbound/defs/TileDefinition.kt | 587 ------------------ .../kstarbound/defs/tile/MaterialModifier.kt | 6 +- .../kstarbound/defs/tile/RenderParameters.kt | 21 +- .../kstarbound/defs/tile/RenderTemplate.kt | 230 +++++-- .../kstarbound/defs/tile/TileDefinition.kt | 50 ++ .../dbotthepony/kstarbound/io/EnumAdapter.kt | 40 ++ .../kstarbound/io/KConcreteTypeAdapter.kt | 14 +- .../dbotthepony/kstarbound/util/WriteOnce.kt | 20 + .../ru/dbotthepony/kstarbound/world/Chunk.kt | 2 +- .../dbotthepony/kstarbound/world/ChunkAPI.kt | 2 +- .../dbotthepony/kstarbound/world/TileView.kt | 2 +- .../ru/dbotthepony/kstarbound/world/World.kt | 2 +- 15 files changed, 351 insertions(+), 755 deletions(-) delete mode 100644 src/main/kotlin/ru/dbotthepony/kstarbound/defs/TileDefinition.kt create mode 100644 src/main/kotlin/ru/dbotthepony/kstarbound/defs/tile/TileDefinition.kt create mode 100644 src/main/kotlin/ru/dbotthepony/kstarbound/io/EnumAdapter.kt create mode 100644 src/main/kotlin/ru/dbotthepony/kstarbound/util/WriteOnce.kt diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/Main.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/Main.kt index 09b0b7fe..1cd080a3 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/Main.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/Main.kt @@ -19,61 +19,9 @@ private val LOGGER = LogManager.getLogger() fun main() { LOGGER.info("Running LWJGL ${Version.getVersion()}") - if (true) { - //val pak = StarboundPak(File("J:\\SteamLibrary\\steamapps\\common\\Starbound\\assets\\packed.pak")) - //val json = JsonParser.parseReader(pak.getReader("/projectiles/traps/lowgravboostergas/lowgravboostergas.projectile")) - //val obj = Gson().fromJson(json, ProjectileDefinitionBuilder::class.java) - //println(obj.build()) - //return - } - val db = BTreeDB(File("F:\\SteamLibrary\\steamapps\\common\\Starbound - Unstable\\storage\\universe\\389760395_938904237_-238610574_5.world")) //val db = BTreeDB(File("world.world")) - /*if (true) { - val a = System.currentTimeMillis() - val worldMeta = db.read(byteArrayOf(0, 0, 0, 0, 0)) - println(System.currentTimeMillis() - a) - - val inflater = Inflater() - inflater.setInput(worldMeta!!) - - val output = ByteArray(1_000_000) - inflater.inflate(output) - - val stream = DataInputStream(ByteArrayInputStream(output)) - println("X tiles ${stream.readInt()}") - println("Y tiles ${stream.readInt()}") - - val metadata = VersionedJSON(stream) - println(metadata.data) - - return - }*/ - - /*if (true) { - val data = db.read(byteArrayOf(1, 0, 61, 0, 23)) - - val inflater = Inflater() - inflater.setInput(data!!) - - val output = ByteArray(64_000) - val actual = inflater.inflate(output) - File("F:\\SteamLibrary\\steamapps\\common\\Starbound - Unstable\\storage\\universe\\tiles.dat").writeBytes(output) - val reader = DataInputStream(ByteArrayInputStream(output)) - - reader.skipBytes(3) - - for (y in 0 .. 31) { - for (x in 0 .. 31) { - println("$x $y ${reader.readShort()}") - reader.skipBytes(29) - } - } - - return - }*/ - val client = StarboundClient() //Starbound.addFilePath(File("./unpacked_assets/")) @@ -91,34 +39,6 @@ fun main() { val ent = PlayerEntity(client.world!!) Starbound.onInitialize { - if (true) { - val input = "{\n" + - " \"modId\" : 26,\n" + - " \"modName\" : \"aegisalt\",\n" + - " \"itemDrop\" : \"aegisaltore\",\n" + - " \"description\" : \"Aegisalt.\",\n" + - " \"health\" : 5,\n" + - " \"harvestLevel\" : 5,\n" + - " \"breaksWithTile\" : true,\n" + - "\n" + - " \"miningSounds\" : [ \"/sfx/tools/pickaxe_ore.ogg\", \"/sfx/tools/pickaxe_ore2.ogg\" ],\n" + - " \"miningParticle\" : \"orespark\",\n" + - "\n" + - " \"renderTemplate\" : \"/tiles/classicmaterialtemplate.config\",\n" + - " \"renderParameters\" : {\n" + - " \"texture\" : \"aegisalt.png\",\n" + - " \"variants\" : 8,\n" + - " \"multiColored\" : false,\n" + - " \"zLevel\" : 0\n" + - " }\n" + - "}\n" - - val json = Starbound.gson.fromJson(input, MaterialModifier::class.java) - - println(json) - return@onInitialize - } - var find = 0L var set = 0L var parse = 0L diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/Starbound.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/Starbound.kt index 914a260d..a73f7d09 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/Starbound.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/Starbound.kt @@ -12,6 +12,7 @@ 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.* @@ -41,6 +42,15 @@ const val PIXELS_IN_STARBOUND_UNITf = 8.0f object Starbound : IVFS { private val LOGGER = LogManager.getLogger() + private val _readingFolder = ThreadLocal() + + /** + * Случит переменной для указания из какой папки происходит чтение asset'а в данном потоке + */ + var readingFolder: String? + get() = _readingFolder.get() + set(value) { _readingFolder.set(value) } + private val tiles = HashMap() private val tilesByMaterialID = Int2ObjectAVLTreeMap() private val projectiles = HashMap() @@ -77,6 +87,7 @@ object Starbound : IVFS { .also(MaterialModifier::registerGson) .also(RenderParameters::registerGson) .also(RenderTemplate::registerGson) + .also(TileDefinition::registerGson) .registerTypeAdapter(DamageType::class.java, CustomEnumTypeAdapter(DamageType.values()).nullSafe()) @@ -231,12 +242,15 @@ object Starbound : IVFS { } private fun loadTileMaterials(callback: (String) -> Unit) { + readingFolder = "/tiles/materials" + for (fs in fileSystems) { for (listedFile in fs.listAllFilesWithExtension("material")) { try { callback("Loading $listedFile") - val tileDef = TileDefinitionBuilder.fromJson(JsonParser.parseReader(getReader(listedFile)) as JsonObject).build("/tiles/materials") + readingFolder = getPathFolder(listedFile) + val tileDef = gson.fromJson(getReader(listedFile), 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!" } @@ -248,6 +262,8 @@ object Starbound : IVFS { } } } + + readingFolder = null } private fun loadProjectiles(callback: (String) -> Unit) { 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 105c21de..8cc150b6 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/client/render/TileRenderer.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/client/render/TileRenderer.kt @@ -5,9 +5,9 @@ import org.lwjgl.opengl.GL46.* import ru.dbotthepony.kstarbound.PIXELS_IN_STARBOUND_UNITf import ru.dbotthepony.kstarbound.Starbound import ru.dbotthepony.kstarbound.client.gl.* -import ru.dbotthepony.kstarbound.defs.TileDefinition -import ru.dbotthepony.kstarbound.defs.TileRenderMatchPiece -import ru.dbotthepony.kstarbound.defs.TileRenderPiece +import ru.dbotthepony.kstarbound.defs.tile.RenderMatch +import ru.dbotthepony.kstarbound.defs.tile.RenderPiece +import ru.dbotthepony.kstarbound.defs.tile.TileDefinition import ru.dbotthepony.kstarbound.world.ITileChunk import ru.dbotthepony.kvector.vector.Color import ru.dbotthepony.kvector.vector.nint.Vector2i @@ -152,7 +152,7 @@ private enum class TileRenderTesselateResult { } class TileRenderer(val state: GLStateTracker, val tile: TileDefinition) { - val texture = state.loadNamedTexture(tile.render.texture).also { + val texture = state.loadNamedTexture(tile.renderParameters.absoluteTexturePath).also { it.textureMagFilter = GL_NEAREST } @@ -160,7 +160,7 @@ class TileRenderer(val state: GLStateTracker, val tile: TileDefinition) { val bakedBackgroundProgramState = state.tileRenderers.background(texture) // private var notifiedDepth = false - private fun tesselateAt(piece: TileRenderPiece, getter: ITileChunk, builder: DynamicVertexBuilder, pos: Vector2i, offset: Vector2i = Vector2i.ZERO) { + private fun tesselateAt(piece: RenderPiece, getter: ITileChunk, builder: DynamicVertexBuilder, pos: Vector2i, offset: Vector2i = Vector2i.ZERO) { val fx = pos.x.toFloat() val fy = pos.y.toFloat() @@ -173,7 +173,7 @@ class TileRenderer(val state: GLStateTracker, val tile: TileDefinition) { if (offset != Vector2i.ZERO) { a += offset.x / PIXELS_IN_STARBOUND_UNITf - // в json файлах y указан как положительный вверх, + // в json файлах Y указан как положительный вверх, // что соответствует нашему миру b += offset.y / PIXELS_IN_STARBOUND_UNITf @@ -181,7 +181,7 @@ class TileRenderer(val state: GLStateTracker, val tile: TileDefinition) { d += offset.y / PIXELS_IN_STARBOUND_UNITf } - if (tile.render.variants == 0 || piece.texture != null || piece.variantStride == null) { + if (tile.renderParameters.variants == 0 || piece.texture != null || piece.variantStride == null) { val (u0, v0) = texture.pixelToUV(piece.texturePosition) val (u1, v1) = texture.pixelToUV(piece.texturePosition + piece.textureSize) @@ -192,7 +192,7 @@ class TileRenderer(val state: GLStateTracker, val tile: TileDefinition) { d, Z_LEVEL, VertexTransformers.uv(u0, v1, u1, v0)) } else { - val variant = (getter.randomDoubleFor(pos) * tile.render.variants).toInt() + val variant = (getter.randomDoubleFor(pos) * tile.renderParameters.variants).toInt() val (u0, v0) = texture.pixelToUV(piece.texturePosition + piece.variantStride * variant) val (u1, v1) = texture.pixelToUV(piece.texturePosition + piece.textureSize + piece.variantStride * variant) @@ -206,19 +206,19 @@ class TileRenderer(val state: GLStateTracker, val tile: TileDefinition) { } } - private fun tesselatePiece(matchPiece: TileRenderMatchPiece, getter: ITileChunk, layers: TileLayerList, pos: Vector2i, thisBuilder: DynamicVertexBuilder, background: Boolean): TileRenderTesselateResult { + private fun tesselatePiece(matchPiece: RenderMatch, getter: ITileChunk, layers: TileLayerList, pos: Vector2i, thisBuilder: DynamicVertexBuilder, background: Boolean): TileRenderTesselateResult { if (matchPiece.test(getter, tile, pos)) { for (renderPiece in matchPiece.pieces) { if (renderPiece.piece.texture != null) { val program: BakedProgramState if (background) { - program = state.tileRenderers.background(state.loadNamedTexture(renderPiece.piece.texture)) + program = state.tileRenderers.background(state.loadNamedTexture(renderPiece.piece.texture!!)) } else { - program = state.tileRenderers.foreground(state.loadNamedTexture(renderPiece.piece.texture)) + program = state.tileRenderers.foreground(state.loadNamedTexture(renderPiece.piece.texture!!)) } - tesselateAt(renderPiece.piece, getter, layers.getLayer(program, tile.render.zLevel) { + tesselateAt(renderPiece.piece, getter, layers.getLayer(program, tile.renderParameters.zLevel) { return@getLayer DynamicVertexBuilder(GLFlatAttributeList.VERTEX_TEXTURE, VertexType.QUADS) }, pos, renderPiece.offset) } else { @@ -256,14 +256,14 @@ class TileRenderer(val state: GLStateTracker, val tile: TileDefinition) { fun tesselate(getter: ITileChunk, layers: TileLayerList, pos: Vector2i, background: Boolean = false) { // если у нас нет renderTemplate // то мы просто не можем его отрисовать - tile.render.renderTemplate ?: return + val template = tile.renderTemplate - val builder = layers.getLayer(if (background) bakedBackgroundProgramState else bakedProgramState, tile.render.zLevel) { + val builder = layers.getLayer(if (background) bakedBackgroundProgramState else bakedProgramState, tile.renderParameters.zLevel) { return@getLayer DynamicVertexBuilder(GLFlatAttributeList.VERTEX_TEXTURE, VertexType.QUADS) } - for ((_, matcher) in tile.render.renderTemplate.matches) { - for (matchPiece in matcher.pieces) { + for ((_, matcher) in template.matches) { + for (matchPiece in matcher) { val matched = tesselatePiece(matchPiece, getter, layers, pos, builder, background) if (matched == TileRenderTesselateResult.HALT) { diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/defs/TileDefinition.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/defs/TileDefinition.kt deleted file mode 100644 index 8cff6210..00000000 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/defs/TileDefinition.kt +++ /dev/null @@ -1,587 +0,0 @@ -package ru.dbotthepony.kstarbound.defs - -import com.google.common.collect.ImmutableList -import com.google.common.collect.ImmutableMap -import com.google.gson.GsonBuilder -import com.google.gson.JsonArray -import com.google.gson.JsonObject -import com.google.gson.JsonPrimitive -import com.google.gson.TypeAdapter -import com.google.gson.internal.bind.TypeAdapters -import com.google.gson.stream.JsonReader -import com.google.gson.stream.JsonWriter -import org.apache.logging.log4j.LogManager -import ru.dbotthepony.kstarbound.Starbound -import ru.dbotthepony.kstarbound.world.ITileGetter -import ru.dbotthepony.kvector.vector.Color -import ru.dbotthepony.kvector.vector.nint.Vector2i - -data class TileDefinition( - val materialId: Int, - val materialName: String, - val particleColor: Color, - val itemDrop: String?, - val description: String, - val shortdescription: String, - val blocksLiquidFlow: Boolean, - val soil: Boolean, - val tillableMod: Int, - - val racialDescription: ImmutableMap, - val footstepSound: String?, - val health: Int, - val category: String, - - val render: TileRenderDefinition -) { - init { - require(materialId >= 0) { "Material ID must be positive ($materialId given) ($materialName)" } - require(materialId != 0) { "Material ID 0 is reserved ($materialName)" } - } -} - -class TileDefinitionBuilder { - var materialId = 0 - var materialName = "unknown_tile" - var particleColor = Color.WHITE - var itemDrop: String? = "unknown" - var description = "..." - var shortdescription = "..." - - var blocksLiquidFlow = true - var soil = false - var tillableMod = 0 - - val racialDescription = ArrayList>() - - var footstepSound: String? = null - var health = 0 - var category = "generic" - - val render = TileRenderDefinitionBuilder() - - fun build(directory: String? = null): TileDefinition { - return TileDefinition( - racialDescription = ImmutableMap.builder().also { - for ((k, v) in this.racialDescription) { - it.put(k, v) - } - }.build(), - - materialId = materialId, - materialName = materialName, - particleColor = particleColor, - itemDrop = itemDrop, - description = description, - shortdescription = shortdescription, - blocksLiquidFlow = blocksLiquidFlow, - soil = soil, - tillableMod = tillableMod, - - footstepSound = footstepSound, - health = health, - category = category, - - render = render.build(directory) - ) - } - - companion object { - private val LOGGER = LogManager.getLogger() - - fun fromJson(input: JsonObject): TileDefinitionBuilder { - val builder = TileDefinitionBuilder() - - try { - builder.materialName = input["materialName"].asString - builder.materialId = input["materialId"].asInt - - require(builder.materialId >= 0) { "Invalid materialId ${builder.materialId}" } - - builder.particleColor = Starbound.gson.fromJson(input["particleColor"], Color::class.java) - builder.itemDrop = input["itemDrop"]?.asString - builder.description = input["description"]?.asString ?: builder.description - builder.shortdescription = input["shortdescription"]?.asString ?: builder.shortdescription - builder.footstepSound = input["footstepSound"]?.asString - builder.blocksLiquidFlow = input["footstepSound"]?.asBoolean ?: builder.blocksLiquidFlow - builder.soil = input["footstepSound"]?.asBoolean ?: builder.soil - builder.health = input["health"].asInt - builder.tillableMod = input["health"]?.asInt ?: builder.tillableMod - builder.category = input["category"].asString - - if (input["variants"] != null) { - LOGGER.warn("Tile {} has `variants` ({}) defined as top level property (expected to be under `renderParameters`)", builder.materialName, input["variants"].asString) - } - - for (key in input.keySet()) { - if (key.endsWith("Description") && key.length != "Description".length) { - builder.racialDescription.add(key.substring(0, key.length - "Description".length) to input[key].asString) - } - } - - input["renderParameters"]?.asJsonObject?.let { - builder.render.texture = it["texture"].asString - builder.render.variants = it["variants"].asInt - builder.render.lightTransparent = it["lightTransparent"]?.asBoolean ?: builder.render.lightTransparent - builder.render.occludesBelow = it["occludesBelow"]?.asBoolean ?: builder.render.occludesBelow - builder.render.multiColored = it["multiColored"]?.asBoolean ?: builder.render.multiColored - builder.render.zLevel = it["zLevel"].asInt - } - - builder.render.renderTemplate = input["renderTemplate"]?.asString?.let renderTemplate@{ - return@renderTemplate TileRenderTemplate.load(it) - } - } catch(err: Throwable) { - throw IllegalArgumentException("Failed reading tile definition ${builder.materialName}", err) - } - - return builder - } - } -} - -/** - * Кусочек рендера тайла - * - * root.pieces[] - */ -data class TileRenderPiece( - val name: String, - val texture: String?, - val textureSize: Vector2i, - val texturePosition: Vector2i, - - val colorStride: Vector2i?, - val variantStride: Vector2i?, -) { - companion object { - fun fromJson(name: String, input: JsonObject): TileRenderPiece { - val texture = input["texture"]?.asString?.let { - if (it[0] != '/') { - throw UnsupportedOperationException("Render piece has not absolute texture path: $it") - } - - return@let it - } - - val textureSize = Starbound.gson.fromJson(input["textureSize"], Vector2i::class.java) - val texturePosition = Starbound.gson.fromJson(input["texturePosition"], Vector2i::class.java) - - val colorStride = input["colorStride"]?.let { Starbound.gson.fromJson(it, Vector2i::class.java) } - val variantStride = input["variantStride"]?.let { Starbound.gson.fromJson(it, Vector2i::class.java) } - - return TileRenderPiece(name, texture, textureSize, texturePosition, colorStride, variantStride) - } - } -} - -/** - * Кусочек правила рендера тайла - * - * root.rules.`name`.entries[] - */ -sealed class RenderRule(params: Map) { - val matchHue = params["matchHue"] as? Boolean ?: false - val inverse = params["inverse"] as? Boolean ?: false - - abstract fun test(getter: ITileGetter, thisRef: TileDefinition, thisPos: Vector2i, offsetPos: Vector2i): Boolean - - companion object { - fun factory(name: String, params: Map): RenderRule { - return when (name) { - "EqualsSelf" -> RenderRuleEqualsSelf(params) - "Shadows" -> RenderRuleShadows(params) - - // неизвестно что оно делает, но вероятнее всего, есть ли там что либо в принципе - "Connects" -> RenderRuleConnects(params) - - else -> throw IllegalArgumentException("Unknown render rule '$name'") - } - } - - fun fromJson(input: JsonObject): RenderRule { - val params = ImmutableMap.builder() - - for (key in input.keySet()) { - if (key != "type") { - val value = input[key] as? JsonPrimitive - - if (value != null) { - if (value.isBoolean) { - params.put(key, value.asBoolean) - } else if (value.isNumber) { - params.put(key, value.asDouble) - } else { - params.put(key, value.asString) - } - } - } - } - - return factory(input["type"].asString, params.build()) - } - } -} - -class RenderRuleEqualsSelf(params: Map) : RenderRule(params) { - override fun test(getter: ITileGetter, thisRef: TileDefinition, thisPos: Vector2i, offsetPos: Vector2i): Boolean { - val otherTile = getter[thisPos + offsetPos] ?: return inverse - - if (inverse) - return otherTile.def != thisRef - - return otherTile.def == thisRef - } -} - -class RenderRuleShadows(params: Map) : RenderRule(params) { - override fun test(getter: ITileGetter, thisRef: TileDefinition, thisPos: Vector2i, offsetPos: Vector2i): Boolean { - return false // TODO - } -} - -class RenderRuleConnects(params: Map) : RenderRule(params) { - override fun test(getter: ITileGetter, thisRef: TileDefinition, thisPos: Vector2i, offsetPos: Vector2i): Boolean { - if (inverse) - return getter[thisPos + offsetPos] == null - - return getter[thisPos + offsetPos] != null - } -} - -class AlwaysPassingRenderRule(params: Map) : RenderRule(params) { - override fun test(getter: ITileGetter, thisRef: TileDefinition, thisPos: Vector2i, offsetPos: Vector2i): Boolean { - return inverse - } -} - -class AlwaysFailingRenderRule(params: Map) : RenderRule(params) { - override fun test(getter: ITileGetter, thisRef: TileDefinition, thisPos: Vector2i, offsetPos: Vector2i): Boolean { - return !inverse - } -} - -enum class RenderRuleCombination { - ALL, - ANY -} - - -/** - * Правило рендера тайла - * - * root.rules[] - */ -data class TileRenderRule( - val name: String, - val join: RenderRuleCombination, - val pieces: List -) { - fun test(getter: ITileGetter, thisRef: TileDefinition, thisPos: Vector2i, offsetPos: Vector2i): Boolean { - if (join == RenderRuleCombination.ANY) { - for (piece in pieces) { - if (piece.test(getter, thisRef, thisPos, offsetPos)) { - return true - } - } - - return false - } else { - for (piece in pieces) { - if (!piece.test(getter, thisRef, thisPos, offsetPos)) { - return false - } - } - - return true - } - } - - companion object { - fun fromJson(name: String, input: JsonObject): TileRenderRule { - val join = input["join"]?.asString?.let { - when (it) { - "any" -> RenderRuleCombination.ANY - else -> RenderRuleCombination.ALL - } - } ?: RenderRuleCombination.ALL - - val jEntries = input["entries"] as JsonArray - val pieces = ArrayList(jEntries.size()) - - for (elem in jEntries) { - pieces.add(RenderRule.fromJson(elem.asJsonObject)) - } - - return TileRenderRule(name, join, ImmutableList.copyOf(pieces)) - } - } -} - -data class TileRenderMatchedPiece( - val piece: TileRenderPiece, - val offset: Vector2i -) { - companion object { - fun fromJson(input: JsonArray, tilePieces: Map): TileRenderMatchedPiece { - val piece = input[0].asString.let { - return@let tilePieces[it] ?: throw IllegalArgumentException("Unable to find render piece $it") - } - - val offset = Starbound.gson.fromJson(input[1], Vector2i::class.java) - return TileRenderMatchedPiece(piece, offset) - } - } -} - -data class TileRenderMatchPositioned( - val condition: TileRenderRule, - val offset: Vector2i -) { - /** - * Состояние [condition] на [thisPos] с [offset] - */ - fun test(getter: ITileGetter, thisRef: TileDefinition, thisPos: Vector2i): Boolean { - return condition.test(getter, thisRef, thisPos, offset) - } - - companion object { - fun fromJson(input: JsonArray, rulePieces: Map): TileRenderMatchPositioned { - val offset = Starbound.gson.fromJson(input[0], Vector2i::class.java) - val condition = rulePieces[input[1].asString] ?: throw IllegalArgumentException("Rule ${input[1].asString} is missing!") - - return TileRenderMatchPositioned(condition, offset) - } - } -} - -data class TileRenderMatchPiece( - val pieces: List, - val matchAllPoints: List, - val matchAnyPoints: List, - val subMatches: List, - val haltOnSubMatch: Boolean, - val haltOnMatch: Boolean -) { - init { - if (matchAnyPoints.isNotEmpty() || matchAllPoints.isNotEmpty()) { - require(matchAnyPoints.isEmpty() || matchAllPoints.isEmpty()) { "Both matchAllPoints and matchAnyPoints are present, this is not valid." } - } - } - - /** - * Возвращает, сработали ли ВСЕ [matchAllPoints] или ЛЮБОЙ ИЗ [matchAnyPoints] - * - * Если хотя бы один из них вернул false, весь тест возвращает false - * - * [subMatches] стоит итерировать только если это вернуло true - */ - fun test(getter: ITileGetter, thisRef: TileDefinition, thisPos: Vector2i): Boolean { - for (matcher in matchAllPoints) { - if (!matcher.test(getter, thisRef, thisPos)) { - return false - } - } - - if (matchAnyPoints.isEmpty()) { - return true - } - - for (matcher in matchAnyPoints) { - if (matcher.test(getter, thisRef, thisPos)) { - return true - } - } - - return false - } - - companion object { - fun fromJson(input: JsonObject, tilePieces: Map, rulePieces: Map): TileRenderMatchPiece { - val pieces = input["pieces"]?.asJsonArray?.let { - val list = ArrayList() - - for (thisPiece in it) { - list.add(TileRenderMatchedPiece.fromJson(thisPiece.asJsonArray, tilePieces)) - } - - return@let ImmutableList.copyOf(list) - } ?: listOf() - - val matchAllPoints = input["matchAllPoints"]?.asJsonArray?.let { - val list = ArrayList() - - for (thisPiece in it) { - list.add(TileRenderMatchPositioned.fromJson(thisPiece.asJsonArray, rulePieces)) - } - - return@let ImmutableList.copyOf(list) - } ?: listOf() - - val matchAnyPoints = input["matchAnyPoints"]?.asJsonArray?.let { - val list = ArrayList() - - for (thisPiece in it) { - list.add(TileRenderMatchPositioned.fromJson(thisPiece.asJsonArray, rulePieces)) - } - - return@let ImmutableList.copyOf(list) - } ?: listOf() - - val subMatches = input["subMatches"]?.asJsonArray?.let { - val list = ArrayList() - - for (thisPiece in it) { - list.add(fromJson(thisPiece.asJsonObject, tilePieces, rulePieces)) - } - - return@let ImmutableList.copyOf(list) - } ?: listOf() - - val haltOnSubMatch = input["haltOnSubMatch"]?.asBoolean ?: false - val haltOnMatch = input["haltOnMatch"]?.asBoolean ?: false - - return TileRenderMatchPiece( - pieces = pieces, - matchAllPoints = matchAllPoints, - matchAnyPoints = matchAnyPoints, - subMatches = subMatches, - - haltOnSubMatch = haltOnSubMatch, - haltOnMatch = haltOnMatch, - ) - } - } -} - -data class TileRenderMatch( - val name: String, - val pieces: List, -) { - companion object { - fun fromJson(input: JsonArray, tilePieces: Map, rulePieces: Map): TileRenderMatch { - val name = input[0].asString - val pieces = ArrayList() - - try { - for (elem in input[1].asJsonArray) { - pieces.add(TileRenderMatchPiece.fromJson(elem.asJsonObject, tilePieces, rulePieces)) - } - } catch(err: Throwable) { - throw IllegalArgumentException("Failed to deserialize render match rule $name", err) - } - - return TileRenderMatch(name, ImmutableList.copyOf(pieces)) - } - } -} - -class TileRenderTemplateParseException(message: String, cause: Throwable?) : IllegalStateException(message, cause) - -data class TileRenderTemplate( - val representativePiece: String, - val pieces: Map, - val rules: Map, - val matches: Map, -) { - companion object { - val map = HashMap() - - fun register(builder: GsonBuilder) { - builder.registerTypeAdapter(TileRenderTemplate::class.java, object : TypeAdapter() { - override fun write(out: JsonWriter?, value: TileRenderTemplate?) { - TODO("Not yet implemented") - } - - override fun read(`in`: JsonReader): TileRenderTemplate { - return fromJson(TypeAdapters.JSON_ELEMENT.read(`in`) as JsonObject) - } - }) - } - - fun load(path: String): TileRenderTemplate { - return map.computeIfAbsent(path) { - try { - val json = Starbound.loadJson(path).asJsonObject - return@computeIfAbsent fromJson(json) - } catch (err: Throwable) { - throw TileRenderTemplateParseException("Failed to load tile render definition from $path", err) - } - } - } - - fun fromJson(input: JsonObject): TileRenderTemplate { - val representativePiece = input["representativePiece"].asString - - val pieces = HashMap() - val rules = HashMap() - val matches = HashMap() - - val jPieces = input["pieces"] as JsonObject - - for (key in jPieces.keySet()) { - pieces[key] = TileRenderPiece.fromJson(key, jPieces[key] as JsonObject) - } - - val jRules = input["rules"] as JsonObject - - for (key in jRules.keySet()) { - rules[key] = TileRenderRule.fromJson(key, jRules[key] as JsonObject) - } - - val jMatches = input["matches"] as JsonArray - - for (instance in jMatches) { - val deserialized = TileRenderMatch.fromJson(instance.asJsonArray, pieces, rules) - matches[deserialized.name] = deserialized - } - - return TileRenderTemplate(representativePiece, ImmutableMap.copyOf(pieces), ImmutableMap.copyOf(rules), ImmutableMap.copyOf(matches)) - } - } -} - -data class TileRenderDefinition( - val texture: String, - val variants: Int, - val lightTransparent: Boolean, - val occludesBelow: Boolean, - val multiColored: Boolean, - val zLevel: Int, - val renderTemplate: TileRenderTemplate? -) - -class TileRenderDefinitionBuilder { - var texture = "" - var variants = 1 - var lightTransparent = false - var occludesBelow = false - var multiColored = false - var zLevel = 0 - var renderTemplate: TileRenderTemplate? = null - - fun build(directory: String? = null): TileRenderDefinition { - val newtexture: String - - if (texture[0] == '/') { - // путь абсолютен - newtexture = texture - } else { - if (directory != null) { - newtexture = "$directory/$texture" - } else { - newtexture = texture - } - } - - return TileRenderDefinition( - texture = newtexture, - variants = variants, - lightTransparent = lightTransparent, - occludesBelow = occludesBelow, - multiColored = multiColored, - zLevel = zLevel, - renderTemplate = renderTemplate, - ) - } -} 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 6947ab04..edc5e353 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/defs/tile/MaterialModifier.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/defs/tile/MaterialModifier.kt @@ -16,6 +16,10 @@ data class MaterialModifier( val renderTemplate: RenderTemplate, val renderParameters: RenderParameters ) { + init { + require(modId > 0) { "Invalid material modifier ID $modId" } + } + companion object { val ADAPTER = KConcreteTypeAdapter.Builder(MaterialModifier::class) .plain(MaterialModifier::modId) @@ -25,7 +29,7 @@ data class MaterialModifier( .plain(MaterialModifier::health) .plain(MaterialModifier::harvestLevel) .plain(MaterialModifier::breaksWithTile) - .list(MaterialModifier::miningSounds, String::class.java) + .list(MaterialModifier::miningSounds, String::class) .plain(MaterialModifier::miningParticle) .plain(MaterialModifier::renderTemplate, RenderTemplate.CACHE) .plain(MaterialModifier::renderParameters) 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 915f5dfb..48b80930 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/defs/tile/RenderParameters.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/defs/tile/RenderParameters.kt @@ -1,20 +1,37 @@ package ru.dbotthepony.kstarbound.defs.tile import com.google.gson.GsonBuilder +import ru.dbotthepony.kstarbound.Starbound import ru.dbotthepony.kstarbound.io.KConcreteTypeAdapter data class RenderParameters( val texture: String, - val variants: Int, - val multiColored: Boolean, + 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.readingFolder + + if (dir == null || texture[0] == '/') { + absoluteTexturePath = texture + } else { + absoluteTexturePath = "$dir/$texture" + } + } + companion object { val ADAPTER = KConcreteTypeAdapter.Builder(RenderParameters::class) .plain( RenderParameters::texture, RenderParameters::variants, RenderParameters::multiColored, + RenderParameters::occludesBelow, + RenderParameters::lightTransparent, RenderParameters::zLevel, ) .build() 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 63377beb..33c6dee8 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/defs/tile/RenderTemplate.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/defs/tile/RenderTemplate.kt @@ -6,9 +6,13 @@ 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 org.apache.logging.log4j.LogManager import ru.dbotthepony.kstarbound.Starbound -import ru.dbotthepony.kstarbound.defs.TileDefinition +import ru.dbotthepony.kstarbound.io.CustomEnumTypeAdapter +import ru.dbotthepony.kstarbound.io.EnumAdapter import ru.dbotthepony.kstarbound.io.KConcreteTypeAdapter +import ru.dbotthepony.kstarbound.util.WriteOnce import ru.dbotthepony.kstarbound.world.ITileGetter import ru.dbotthepony.kvector.vector.nint.Vector2i import java.util.concurrent.ConcurrentHashMap @@ -33,80 +37,184 @@ data class RenderPiece( } } -data class RenderRule( - val type: String, - val matchHue: Boolean = false, - val inverse: Boolean = false, -) { - private fun doTest(getter: ITileGetter, thisRef: TileDefinition, thisPos: Vector2i, offsetPos: Vector2i): Boolean { - return when (type) { - "EqualsSelf" -> getter[thisPos + offsetPos]?.def == thisRef - "Connects" -> getter[thisPos + offsetPos] != null - - else -> false - } - } - - fun test(getter: ITileGetter, thisRef: TileDefinition, thisPos: Vector2i, offsetPos: Vector2i): Boolean { - if (inverse) { - return !doTest(getter, thisRef, thisPos, offsetPos) - } - - return doTest(getter, thisRef, thisPos, offsetPos) - } - - companion object { - val ADAPTER = KConcreteTypeAdapter.Builder(RenderRule::class) - .plain( - RenderRule::type, - RenderRule::matchHue, - RenderRule::inverse, - ) - .build() - } -} - data class RenderRuleList( - val entries: List + val entries: List, + val join: Combination = Combination.ALL ) { + enum class Combination { + ALL, ANY; + } + + data class Entry( + val type: String, + val matchHue: Boolean = false, + val inverse: Boolean = false, + ) { + private fun doTest(getter: ITileGetter, thisRef: TileDefinition, thisPos: Vector2i, offsetPos: Vector2i): Boolean { + return when (type) { + "EqualsSelf" -> getter[thisPos + offsetPos]?.def == thisRef + "Connects" -> getter[thisPos + offsetPos] != null + + else -> { + if (LOGGED.add(type)) { + LOGGER.error("Unknown render rule test $type!") + } + + false + } + } + } + + fun test(getter: ITileGetter, thisRef: TileDefinition, thisPos: Vector2i, offsetPos: Vector2i): Boolean { + if (inverse) { + return !doTest(getter, thisRef, thisPos, offsetPos) + } + + return doTest(getter, thisRef, thisPos, offsetPos) + } + + companion object { + private val LOGGER = LogManager.getLogger() + private val LOGGED = ObjectArraySet() + + val ADAPTER = KConcreteTypeAdapter.Builder(Entry::class) + .plain( + Entry::type, + Entry::matchHue, + Entry::inverse, + ) + .build() + } + } + + fun test(getter: ITileGetter, thisRef: TileDefinition, thisPos: Vector2i, offset: Vector2i): Boolean { + when (join) { + Combination.ALL -> { + for (entry in entries) { + if (!entry.test(getter, thisRef, thisPos, offset)) { + return false + } + } + + return true + } + + Combination.ANY -> { + for (entry in entries) { + if (entry.test(getter, thisRef, thisPos, offset)) { + return true + } + } + + return false + } + } + } + companion object { val ADAPTER = KConcreteTypeAdapter.Builder(RenderRuleList::class) - .list(RenderRuleList::entries, RenderRule::class) + .list(RenderRuleList::entries, Entry::class) + .plain(RenderRuleList::join) .build() } } data class RenderMatch( - val pieces: List? = null, - val matchAllPoints: List? = null, - val subMatches: List? = null, - val haltOnMatch: Boolean = false + val pieces: List = listOf(), + val matchAllPoints: List = listOf(), + val matchAnyPoints: List = listOf(), + val subMatches: List = listOf(), + val haltOnMatch: Boolean = false, + val haltOnSubMatch: Boolean = false, ) { data class Piece( val name: String, - val point: Vector2i - ) + val offset: Vector2i + ) { + var piece by WriteOnce() + + fun resolve(template: RenderTemplate) { + piece = template.pieces[name] ?: throw IllegalStateException("Unable to find render piece $name") + } + } data class Matcher( - val point: Vector2i, + val offset: Vector2i, val ruleName: String - ) + ) { + var rule by WriteOnce() + + fun test(getter: ITileGetter, thisRef: TileDefinition, thisPos: Vector2i): Boolean { + return rule.test(getter, thisRef, thisPos, offset) + } + + fun resolve(template: RenderTemplate) { + rule = template.rules[ruleName] ?: throw IllegalStateException("Unable to find render rule $ruleName") + } + } + + fun resolve(template: RenderTemplate) { + for (value in matchAllPoints) { + value.resolve(template) + } + + for (value in pieces) { + value.resolve(template) + } + + for (value in matchAnyPoints) { + value.resolve(template) + } + + for (value in subMatches) { + value.resolve(template) + } + } + + /** + * Возвращает, сработали ли ВСЕ [matchAllPoints] или ЛЮБОЙ ИЗ [matchAnyPoints] + * + * Если хотя бы один из них вернул false, весь тест возвращает false + * + * [subMatches] стоит итерировать только если это вернуло true + */ + fun test(getter: ITileGetter, thisRef: TileDefinition, thisPos: Vector2i): Boolean { + for (matcher in matchAllPoints) { + if (!matcher.test(getter, thisRef, thisPos)) { + return false + } + } + + if (matchAnyPoints.isEmpty()) { + return true + } + + for (matcher in matchAnyPoints) { + if (matcher.test(getter, thisRef, thisPos)) { + return true + } + } + + return false + } companion object { val ADAPTER = KConcreteTypeAdapter.Builder(RenderMatch::class) .list(RenderMatch::pieces, Piece::class) .list(RenderMatch::matchAllPoints, Matcher::class) + .list(RenderMatch::matchAnyPoints, Matcher::class) .list(RenderMatch::subMatches, RenderMatch::class) .plain(RenderMatch::haltOnMatch) + .plain(RenderMatch::haltOnSubMatch) .build() val PIECE_ADAPTER = KConcreteTypeAdapter.Builder(Piece::class) .plain(Piece::name) - .plain(Piece::point) + .plain(Piece::offset) .build(asList = true) val MATCHER_ADAPTER = KConcreteTypeAdapter.Builder(Matcher::class) - .plain(Matcher::point) + .plain(Matcher::offset) .plain(Matcher::ruleName) .build(asList = true) } @@ -116,6 +224,12 @@ data class RenderMatchList( val name: String, val list: List ) { + fun resolve(template: RenderTemplate) { + for (value in list) { + value.resolve(template) + } + } + companion object { val ADAPTER = KConcreteTypeAdapter.Builder(RenderMatchList::class) .plain(RenderMatchList::name) @@ -127,18 +241,26 @@ data class RenderMatchList( data class RenderTemplate( val pieces: Map, val representativePiece: String, - val matches: List + val matches: List, + val rules: Map, ) { + init { + for (value in matches) { + value.resolve(this) + } + } + companion object { val ADAPTER = KConcreteTypeAdapter.Builder(RenderTemplate::class) .map(RenderTemplate::pieces, RenderPiece::class.java) .plain(RenderTemplate::representativePiece) .list(RenderTemplate::matches, RenderMatchList::class.java) + .map(RenderTemplate::rules, RenderRuleList::class.java) .build() fun registerGson(gsonBuilder: GsonBuilder) { RenderPiece.ADAPTER.register(gsonBuilder) - RenderRule.ADAPTER.register(gsonBuilder) + RenderRuleList.Entry.ADAPTER.register(gsonBuilder) RenderRuleList.ADAPTER.register(gsonBuilder) RenderMatch.ADAPTER.register(gsonBuilder) RenderMatch.PIECE_ADAPTER.register(gsonBuilder) @@ -146,17 +268,11 @@ data class RenderTemplate( RenderMatchList.ADAPTER.register(gsonBuilder) ADAPTER.register(gsonBuilder) + + gsonBuilder.registerTypeAdapter(RenderRuleList.Combination::class.java, EnumAdapter(RenderRuleList.Combination::class.java)) } private val cache = ConcurrentHashMap() - private val _readingFolder = ThreadLocal() - - /** - * Случит переменной для указания из какой папки происходит чтение в данном потоке - */ - var readingFolder: String? - get() = _readingFolder.get() - set(value) { _readingFolder.set(value) } val CACHE = object : TypeAdapter() { override fun write(out: JsonWriter, value: RenderTemplate) { @@ -173,7 +289,7 @@ data class RenderTemplate( if (path[0] != '/') { // относительный путь - val readingFolder = readingFolder ?: throw NullPointerException("Currently read folder is not specified") + val readingFolder = Starbound.readingFolder ?: throw NullPointerException("Currently read folder is not specified") path = "$readingFolder/$path" } diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/defs/tile/TileDefinition.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/defs/tile/TileDefinition.kt new file mode 100644 index 00000000..6125c8d9 --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/defs/tile/TileDefinition.kt @@ -0,0 +1,50 @@ +package ru.dbotthepony.kstarbound.defs.tile + +import com.google.gson.GsonBuilder +import ru.dbotthepony.kstarbound.io.KConcreteTypeAdapter +import ru.dbotthepony.kvector.vector.Color + +data class TileDefinition( + val materialId: Int, + val materialName: String, + val particleColor: Color? = null, + val itemDrop: String? = null, + val description: String = "...", + val shortdescription: String = "...", + val footstepSound: String? = null, + + val blocksLiquidFlow: Boolean = true, + val soil: Boolean = false, + + val health: Double = 0.0, + val category: String, + + val renderTemplate: RenderTemplate, + val renderParameters: RenderParameters, +) { + companion object { + val ADAPTER = KConcreteTypeAdapter.Builder(TileDefinition::class) + .plain( + TileDefinition::materialId, + TileDefinition::materialName, + TileDefinition::particleColor, + TileDefinition::itemDrop, + TileDefinition::description, + TileDefinition::shortdescription, + TileDefinition::footstepSound, + TileDefinition::blocksLiquidFlow, + TileDefinition::soil, + TileDefinition::health, + TileDefinition::category + ) + .plain(TileDefinition::renderTemplate, RenderTemplate.CACHE) + .plain( + TileDefinition::renderParameters, + ) + .build() + + fun registerGson(gsonBuilder: GsonBuilder) { + ADAPTER.register(gsonBuilder) + } + } +} diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/io/EnumAdapter.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/io/EnumAdapter.kt new file mode 100644 index 00000000..64f9612a --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/io/EnumAdapter.kt @@ -0,0 +1,40 @@ +package ru.dbotthepony.kstarbound.io + +import com.google.common.collect.ImmutableMap +import com.google.gson.JsonSyntaxException +import com.google.gson.TypeAdapter +import com.google.gson.stream.JsonReader +import com.google.gson.stream.JsonWriter +import it.unimi.dsi.fastutil.objects.Object2ObjectArrayMap + +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 + + val spaced = value.name.replace('_', ' ') + val stitched = value.name.replace("_", "") + + it[spaced] = value + it[spaced.uppercase()] = value + it[spaced.lowercase()] = value + + it[stitched] = value + it[stitched.uppercase()] = value + it[stitched.lowercase()] = value + } + + ImmutableMap.copyOf(it) + } + + override fun write(out: JsonWriter, value: T) { + out.value(value.name) + } + + override fun read(`in`: JsonReader): T { + val key = `in`.nextString() + return mapping[key] ?: throw JsonSyntaxException("Unable to match '$key' against ${enum.canonicalName}") + } +} diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/io/KConcreteTypeAdapter.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/io/KConcreteTypeAdapter.kt index 1ba47d5e..deeec68e 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/io/KConcreteTypeAdapter.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/io/KConcreteTypeAdapter.kt @@ -294,7 +294,7 @@ class KConcreteTypeAdapter( readValues[fieldId] = adapter.read(reader) presentValues[fieldId] = true } catch(err: Throwable) { - throw JsonSyntaxException("Exception reading field ${field.name}", err) + throw JsonSyntaxException("Exception reading field ${field.name} for ${bound.qualifiedName}", err) } fieldId++ @@ -317,7 +317,7 @@ class KConcreteTypeAdapter( readValues[fieldId] = adapter.read(reader) presentValues[fieldId] = true } catch(err: Throwable) { - throw JsonSyntaxException("Exception reading field ${field.name}", err) + throw JsonSyntaxException("Exception reading field ${field.name} for ${bound.qualifiedName}", err) } } } @@ -341,11 +341,11 @@ class KConcreteTypeAdapter( if (readValues[i] == null) { if (!returnTypeCache[field]!!.isMarkedNullable) { - throw JsonSyntaxException("Field ${field.name} does not accept nulls") + throw JsonSyntaxException("Field ${field.name} of ${bound.qualifiedName} does not accept nulls") } if (!regularFactory.parameters[i].isOptional && !presentValues[i]) { - throw JsonSyntaxException("Field ${field.name} must be defined (even just as null)") + throw JsonSyntaxException("Field ${field.name} of ${bound.qualifiedName} must be defined (even just as null)") } } } @@ -388,7 +388,7 @@ class KConcreteTypeAdapter( val param = regularFactory.parameters[i] if (!param.isOptional && !presentValues[i]) { - throw JsonSyntaxException("Field ${field.name} is missing") + throw JsonSyntaxException("Field ${field.name} of ${bound.qualifiedName} is missing") } if (returnTypeCache[field]!!.isMarkedNullable) { @@ -396,11 +396,11 @@ class KConcreteTypeAdapter( } if (param.isOptional && !presentValues[i]) { - copied[i] = syntheticPrimitives[i] ?: throw NullPointerException("HOW $i") + copied[i] = syntheticPrimitives[i] continue } - throw JsonSyntaxException("Field ${field.name} does not accept nulls") + throw JsonSyntaxException("Field ${field.name} of ${bound.qualifiedName} does not accept nulls") } return syntheticFactory.newInstance(*copied) diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/util/WriteOnce.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/util/WriteOnce.kt new file mode 100644 index 00000000..70dfe36c --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/util/WriteOnce.kt @@ -0,0 +1,20 @@ +package ru.dbotthepony.kstarbound.util + +import kotlin.properties.ReadWriteProperty +import kotlin.reflect.KProperty + +class WriteOnce : ReadWriteProperty { + private var value: V? = null + + override fun getValue(thisRef: Any, property: KProperty<*>): V { + return value ?: throw IllegalStateException("Property ${property.name} is not initialized") + } + + override fun setValue(thisRef: Any, property: KProperty<*>, value: V) { + if (this.value != null) { + throw IllegalStateException("Already initialized") + } + + this.value = value + } +} diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/world/Chunk.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/world/Chunk.kt index 166fb39c..9f4167a2 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/world/Chunk.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/world/Chunk.kt @@ -4,7 +4,7 @@ import ru.dbotthepony.kbox2d.api.BodyDef import ru.dbotthepony.kbox2d.api.FixtureDef import ru.dbotthepony.kbox2d.collision.shapes.PolygonShape import ru.dbotthepony.kbox2d.dynamics.B2Fixture -import ru.dbotthepony.kstarbound.defs.TileDefinition +import ru.dbotthepony.kstarbound.defs.tile.TileDefinition import ru.dbotthepony.kstarbound.world.entities.Entity import ru.dbotthepony.kstarbound.world.phys.RectTileFlooderDepthFirst import ru.dbotthepony.kstarbound.world.phys.RectTileFlooderSizeFirst diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/world/ChunkAPI.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/world/ChunkAPI.kt index e063e620..c951244c 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/world/ChunkAPI.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/world/ChunkAPI.kt @@ -1,6 +1,6 @@ package ru.dbotthepony.kstarbound.world -import ru.dbotthepony.kstarbound.defs.TileDefinition +import ru.dbotthepony.kstarbound.defs.tile.TileDefinition import ru.dbotthepony.kvector.vector.nint.Vector2i const val CHUNK_SHIFT = 5 diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/world/TileView.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/world/TileView.kt index 61455f1d..b83db973 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/world/TileView.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/world/TileView.kt @@ -1,6 +1,6 @@ package ru.dbotthepony.kstarbound.world -import ru.dbotthepony.kstarbound.defs.TileDefinition +import ru.dbotthepony.kstarbound.defs.tile.TileDefinition /** * Предоставляет доступ к чанку и его соседям diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/world/World.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/world/World.kt index bfc96c9e..e96ebe2e 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/world/World.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/world/World.kt @@ -10,7 +10,7 @@ import ru.dbotthepony.kbox2d.dynamics.B2Fixture import ru.dbotthepony.kbox2d.dynamics.B2World import ru.dbotthepony.kbox2d.dynamics.contact.AbstractContact import ru.dbotthepony.kstarbound.METRES_IN_STARBOUND_UNIT -import ru.dbotthepony.kstarbound.defs.TileDefinition +import ru.dbotthepony.kstarbound.defs.tile.TileDefinition import ru.dbotthepony.kstarbound.math.* import ru.dbotthepony.kstarbound.util.Timer import ru.dbotthepony.kstarbound.world.entities.CollisionResolution