From d3396ddb7c92dcb05bd19f5347860c0aa09f0037 Mon Sep 17 00:00:00 2001
From: DBotThePony <dbotthepony@yandex.ru>
Date: Fri, 26 Aug 2022 20:44:00 +0700
Subject: [PATCH] KConcreteTypeAdapter functional

---
 .../kotlin/ru/dbotthepony/kstarbound/Main.kt  |  72 ++-
 .../ru/dbotthepony/kstarbound/Starbound.kt    |   5 +
 .../MaterialModifier.kt}                      |  12 +-
 .../kstarbound/defs/tile/RenderParameters.kt  |  26 ++
 .../kstarbound/defs/tile/RenderTemplate.kt    | 186 ++++++++
 .../kstarbound/io/KConcreteTypeAdapter.kt     | 420 ++++++++++++------
 6 files changed, 528 insertions(+), 193 deletions(-)
 rename src/main/kotlin/ru/dbotthepony/kstarbound/defs/{MaterialModifiers.kt => tile/MaterialModifier.kt} (74%)
 create mode 100644 src/main/kotlin/ru/dbotthepony/kstarbound/defs/tile/RenderParameters.kt
 create mode 100644 src/main/kotlin/ru/dbotthepony/kstarbound/defs/tile/RenderTemplate.kt

diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/Main.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/Main.kt
index 35a1009b..09b0b7fe 100644
--- a/src/main/kotlin/ru/dbotthepony/kstarbound/Main.kt
+++ b/src/main/kotlin/ru/dbotthepony/kstarbound/Main.kt
@@ -1,31 +1,17 @@
 package ru.dbotthepony.kstarbound
 
-import com.google.gson.JsonElement
-import com.google.gson.JsonObject
 import org.apache.logging.log4j.LogManager
 import org.lwjgl.Version
 import org.lwjgl.glfw.GLFW.glfwSetWindowShouldClose
-import ru.dbotthepony.kbox2d.api.*
-import ru.dbotthepony.kbox2d.collision.shapes.CircleShape
-import ru.dbotthepony.kbox2d.collision.shapes.PolygonShape
 import ru.dbotthepony.kstarbound.client.StarboundClient
-import ru.dbotthepony.kstarbound.defs.MaterialModifier
-import ru.dbotthepony.kstarbound.defs.TileDefinition
-import ru.dbotthepony.kstarbound.defs.projectile.ProjectilePhysics
-import ru.dbotthepony.kstarbound.defs.world.dungeon.DungeonWorldDef
+import ru.dbotthepony.kstarbound.defs.tile.MaterialModifier
 import ru.dbotthepony.kstarbound.io.*
-import ru.dbotthepony.kstarbound.world.CHUNK_SIZE_FF
-import ru.dbotthepony.kstarbound.world.Chunk
 import ru.dbotthepony.kstarbound.world.ChunkPos
-import ru.dbotthepony.kstarbound.world.entities.Move
 import ru.dbotthepony.kstarbound.world.entities.PlayerEntity
-import ru.dbotthepony.kstarbound.world.entities.projectile.Projectile
 import ru.dbotthepony.kvector.vector.ndouble.Vector2d
 import java.io.ByteArrayInputStream
 import java.io.DataInputStream
 import java.io.File
-import java.io.InputStream
-import java.util.*
 import java.util.zip.Inflater
 
 private val LOGGER = LogManager.getLogger()
@@ -41,34 +27,6 @@ fun main() {
 		//return
 	}
 
-	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
-	}
-
 	val db = BTreeDB(File("F:\\SteamLibrary\\steamapps\\common\\Starbound - Unstable\\storage\\universe\\389760395_938904237_-238610574_5.world"))
 	//val db = BTreeDB(File("world.world"))
 
@@ -133,6 +91,34 @@ 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 334314ed..914a260d 100644
--- a/src/main/kotlin/ru/dbotthepony/kstarbound/Starbound.kt
+++ b/src/main/kotlin/ru/dbotthepony/kstarbound/Starbound.kt
@@ -9,6 +9,9 @@ import ru.dbotthepony.kstarbound.api.getPathFilename
 import ru.dbotthepony.kstarbound.api.getPathFolder
 import ru.dbotthepony.kstarbound.defs.*
 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.world.SkyParameters
 import ru.dbotthepony.kstarbound.defs.world.dungeon.DungeonWorldDef
 import ru.dbotthepony.kstarbound.io.*
@@ -72,6 +75,8 @@ object Starbound : IVFS {
 		.also(ParallaxPrototype::registerGson)
 		.also(JsonFunction::registerGson)
 		.also(MaterialModifier::registerGson)
+		.also(RenderParameters::registerGson)
+		.also(RenderTemplate::registerGson)
 
 		.registerTypeAdapter(DamageType::class.java, CustomEnumTypeAdapter(DamageType.values()).nullSafe())
 
diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/defs/MaterialModifiers.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/defs/tile/MaterialModifier.kt
similarity index 74%
rename from src/main/kotlin/ru/dbotthepony/kstarbound/defs/MaterialModifiers.kt
rename to src/main/kotlin/ru/dbotthepony/kstarbound/defs/tile/MaterialModifier.kt
index 76458758..6947ab04 100644
--- a/src/main/kotlin/ru/dbotthepony/kstarbound/defs/MaterialModifiers.kt
+++ b/src/main/kotlin/ru/dbotthepony/kstarbound/defs/tile/MaterialModifier.kt
@@ -1,4 +1,4 @@
-package ru.dbotthepony.kstarbound.defs
+package ru.dbotthepony.kstarbound.defs.tile
 
 import com.google.gson.GsonBuilder
 import ru.dbotthepony.kstarbound.io.KConcreteTypeAdapter
@@ -11,9 +11,10 @@ data class MaterialModifier(
 	val health: Int,
 	val harvestLevel: Int,
 	val breaksWithTile: Boolean,
-	val miningSounds: List<String>,
-	val miningParticle: String,
-	val renderTemplate: String,
+	val miningSounds: List<String> = listOf(),
+	val miningParticle: String? = null,
+	val renderTemplate: RenderTemplate,
+	val renderParameters: RenderParameters
 ) {
 	companion object {
 		val ADAPTER = KConcreteTypeAdapter.Builder(MaterialModifier::class)
@@ -26,7 +27,8 @@ data class MaterialModifier(
 			.plain(MaterialModifier::breaksWithTile)
 			.list(MaterialModifier::miningSounds, String::class.java)
 			.plain(MaterialModifier::miningParticle)
-			.plain(MaterialModifier::renderTemplate)
+			.plain(MaterialModifier::renderTemplate, RenderTemplate.CACHE)
+			.plain(MaterialModifier::renderParameters)
 			.build()
 
 		fun registerGson(gsonBuilder: GsonBuilder) {
diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/defs/tile/RenderParameters.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/defs/tile/RenderParameters.kt
new file mode 100644
index 00000000..915f5dfb
--- /dev/null
+++ b/src/main/kotlin/ru/dbotthepony/kstarbound/defs/tile/RenderParameters.kt
@@ -0,0 +1,26 @@
+package ru.dbotthepony.kstarbound.defs.tile
+
+import com.google.gson.GsonBuilder
+import ru.dbotthepony.kstarbound.io.KConcreteTypeAdapter
+
+data class RenderParameters(
+	val texture: String,
+	val variants: Int,
+	val multiColored: Boolean,
+	val zLevel: Int,
+) {
+	companion object {
+		val ADAPTER = KConcreteTypeAdapter.Builder(RenderParameters::class)
+			.plain(
+				RenderParameters::texture,
+				RenderParameters::variants,
+				RenderParameters::multiColored,
+				RenderParameters::zLevel,
+			)
+			.build()
+
+		fun registerGson(gsonBuilder: GsonBuilder) {
+			gsonBuilder.registerTypeAdapter(RenderParameters::class.java, ADAPTER)
+		}
+	}
+}
diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/defs/tile/RenderTemplate.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/defs/tile/RenderTemplate.kt
new file mode 100644
index 00000000..63377beb
--- /dev/null
+++ b/src/main/kotlin/ru/dbotthepony/kstarbound/defs/tile/RenderTemplate.kt
@@ -0,0 +1,186 @@
+package ru.dbotthepony.kstarbound.defs.tile
+
+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 ru.dbotthepony.kstarbound.Starbound
+import ru.dbotthepony.kstarbound.defs.TileDefinition
+import ru.dbotthepony.kstarbound.io.KConcreteTypeAdapter
+import ru.dbotthepony.kstarbound.world.ITileGetter
+import ru.dbotthepony.kvector.vector.nint.Vector2i
+import java.util.concurrent.ConcurrentHashMap
+
+data class RenderPiece(
+	val texture: String? = null,
+	val textureSize: Vector2i,
+	val texturePosition: Vector2i,
+	val colorStride: Vector2i? = null,
+	val variantStride: Vector2i? = null,
+) {
+	companion object {
+		val ADAPTER = KConcreteTypeAdapter.Builder(RenderPiece::class)
+			.plain(
+				RenderPiece::texture,
+				RenderPiece::textureSize,
+				RenderPiece::texturePosition,
+				RenderPiece::colorStride,
+				RenderPiece::variantStride,
+			)
+			.build()
+	}
+}
+
+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<RenderRule>
+) {
+	companion object {
+		val ADAPTER = KConcreteTypeAdapter.Builder(RenderRuleList::class)
+			.list(RenderRuleList::entries, RenderRule::class)
+			.build()
+	}
+}
+
+data class RenderMatch(
+	val pieces: List<Piece>? = null,
+	val matchAllPoints: List<Matcher>? = null,
+	val subMatches: List<RenderMatch>? = null,
+	val haltOnMatch: Boolean = false
+) {
+	data class Piece(
+		val name: String,
+		val point: Vector2i
+	)
+
+	data class Matcher(
+		val point: Vector2i,
+		val ruleName: String
+	)
+
+	companion object {
+		val ADAPTER = KConcreteTypeAdapter.Builder(RenderMatch::class)
+			.list(RenderMatch::pieces, Piece::class)
+			.list(RenderMatch::matchAllPoints, Matcher::class)
+			.list(RenderMatch::subMatches, RenderMatch::class)
+			.plain(RenderMatch::haltOnMatch)
+			.build()
+
+		val PIECE_ADAPTER = KConcreteTypeAdapter.Builder(Piece::class)
+			.plain(Piece::name)
+			.plain(Piece::point)
+			.build(asList = true)
+
+		val MATCHER_ADAPTER = KConcreteTypeAdapter.Builder(Matcher::class)
+			.plain(Matcher::point)
+			.plain(Matcher::ruleName)
+			.build(asList = true)
+	}
+}
+
+data class RenderMatchList(
+	val name: String,
+	val list: List<RenderMatch>
+) {
+	companion object {
+		val ADAPTER = KConcreteTypeAdapter.Builder(RenderMatchList::class)
+			.plain(RenderMatchList::name)
+			.list(RenderMatchList::list, RenderMatch::class)
+			.build(asList = true)
+	}
+}
+
+data class RenderTemplate(
+	val pieces: Map<String, RenderPiece>,
+	val representativePiece: String,
+	val matches: List<RenderMatchList>
+) {
+	companion object {
+		val ADAPTER = KConcreteTypeAdapter.Builder(RenderTemplate::class)
+			.map(RenderTemplate::pieces, RenderPiece::class.java)
+			.plain(RenderTemplate::representativePiece)
+			.list(RenderTemplate::matches, RenderMatchList::class.java)
+			.build()
+
+		fun registerGson(gsonBuilder: GsonBuilder) {
+			RenderPiece.ADAPTER.register(gsonBuilder)
+			RenderRule.ADAPTER.register(gsonBuilder)
+			RenderRuleList.ADAPTER.register(gsonBuilder)
+			RenderMatch.ADAPTER.register(gsonBuilder)
+			RenderMatch.PIECE_ADAPTER.register(gsonBuilder)
+			RenderMatch.MATCHER_ADAPTER.register(gsonBuilder)
+			RenderMatchList.ADAPTER.register(gsonBuilder)
+
+			ADAPTER.register(gsonBuilder)
+		}
+
+		private val cache = ConcurrentHashMap<String, RenderTemplate>()
+		private val _readingFolder = ThreadLocal<String>()
+
+		/**
+		 * Случит переменной для указания из какой папки происходит чтение в данном потоке
+		 */
+		var readingFolder: String?
+			get() = _readingFolder.get()
+			set(value) { _readingFolder.set(value) }
+
+		val CACHE = object : TypeAdapter<RenderTemplate>() {
+			override fun write(out: JsonWriter, value: RenderTemplate) {
+				ADAPTER.write(out, value)
+			}
+
+			override fun read(reader: JsonReader): RenderTemplate {
+				if (reader.peek() != JsonToken.STRING) {
+					throw JsonSyntaxException("Expected string as input for render template cache retriever")
+				}
+
+				var path = reader.nextString()
+
+				if (path[0] != '/') {
+					// относительный путь
+
+					val readingFolder = readingFolder ?: throw NullPointerException("Currently read folder is not specified")
+					path = "$readingFolder/$path"
+				}
+
+				return cache.computeIfAbsent(path) {
+					return@computeIfAbsent Starbound.gson.fromJson(Starbound.getReader(it), RenderTemplate::class.java)
+				}
+			}
+		}
+	}
+}
diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/io/KConcreteTypeAdapter.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/io/KConcreteTypeAdapter.kt
index 3c8c0556..1ba47d5e 100644
--- a/src/main/kotlin/ru/dbotthepony/kstarbound/io/KConcreteTypeAdapter.kt
+++ b/src/main/kotlin/ru/dbotthepony/kstarbound/io/KConcreteTypeAdapter.kt
@@ -2,7 +2,7 @@ package ru.dbotthepony.kstarbound.io
 
 import com.google.common.collect.ImmutableList
 import com.google.common.collect.ImmutableMap
-import com.google.gson.JsonElement
+import com.google.gson.GsonBuilder
 import com.google.gson.JsonSyntaxException
 import com.google.gson.TypeAdapter
 import com.google.gson.internal.bind.TypeAdapters
@@ -14,24 +14,15 @@ import it.unimi.dsi.fastutil.objects.Object2ObjectArrayMap
 import it.unimi.dsi.fastutil.objects.ObjectArraySet
 import org.apache.logging.log4j.LogManager
 import ru.dbotthepony.kstarbound.Starbound
-import kotlin.reflect.KClass
-import kotlin.reflect.KFunction
-import kotlin.reflect.KProperty1
-import kotlin.reflect.KType
+import java.lang.reflect.Constructor
+import kotlin.jvm.internal.DefaultConstructorMarker
+import kotlin.reflect.*
 import kotlin.reflect.full.isSuperclassOf
 import kotlin.reflect.full.isSupertypeOf
+import kotlin.reflect.full.memberProperties
 
-private class PassthroughAdapter<T : Any?>(private val bound: Class<T>) : TypeAdapter<T?>() {
-	override fun write(out: JsonWriter, value: T?) {
-		Starbound.gson.toJson(Starbound.gson.toJsonTree(value, bound) as JsonElement, out)
-	}
-
-	override fun read(reader: JsonReader): T? {
-		return Starbound.gson.fromJson(reader, bound)
-	}
-}
-
-private fun <T> resolveBound(bound: Class<T>): TypeAdapter<T>? {
+@Suppress("unchecked_cast")
+private fun <T> resolveBound(bound: Class<T>): TypeAdapter<T> {
 	return when (bound) {
 		Float::class.java -> TypeAdapters.FLOAT as TypeAdapter<T>
 		Double::class.java -> TypeAdapters.DOUBLE as TypeAdapter<T>
@@ -53,14 +44,8 @@ class ListAdapter<T>(private val bound: Class<T>) : TypeAdapter<List<T>>() {
 
 		val resolvedBound = resolvedBound
 
-		if (resolvedBound != null) {
-			for (v in value) {
-				resolvedBound.write(out, v)
-			}
-		} else {
-			for (v in value) {
-				Starbound.gson.toJson(Starbound.gson.toJsonTree(v, bound) as JsonElement, out)
-			}
+		for (v in value) {
+			resolvedBound.write(out, v)
 		}
 
 		out.endArray()
@@ -72,16 +57,9 @@ class ListAdapter<T>(private val bound: Class<T>) : TypeAdapter<List<T>>() {
 		val builder = ImmutableList.builder<T>()
 		val resolvedBound = resolvedBound
 
-		if (resolvedBound != null) {
-			while (reader.peek() != JsonToken.END_ARRAY) {
-				val readObject = resolvedBound.read(reader) ?: throw JsonSyntaxException("List does not accept nulls")
-				builder.add(readObject as T)
-			}
-		} else {
-			while (reader.peek() != JsonToken.END_ARRAY) {
-				val readObject = Starbound.gson.fromJson<T>(reader, bound) ?: throw JsonSyntaxException("List does not accept nulls")
-				builder.add(readObject)
-			}
+		while (reader.peek() != JsonToken.END_ARRAY) {
+			val readObject = resolvedBound.read(reader) ?: throw JsonSyntaxException("List does not accept nulls")
+			builder.add(readObject as T)
 		}
 
 		reader.endArray()
@@ -105,34 +83,11 @@ class MapAdapter<K, V>(private val boundKey: Class<K>, private val boundValue: C
 		val resolvedKey = resolvedKey
 		val resolvedValue = resolvedValue
 
-		if (resolvedKey != null && resolvedValue != null) {
-			for ((k, v) in value) {
-				out.beginArray()
-				resolvedKey.write(out, k)
-				resolvedValue.write(out, v)
-				out.endArray()
-			}
-		} else if (resolvedKey != null) {
-			for ((k, v) in value) {
-				out.beginArray()
-				resolvedKey.write(out, k)
-				Starbound.gson.toJson(Starbound.gson.toJsonTree(v, boundValue) as JsonElement, out)
-				out.endArray()
-			}
-		} else if (resolvedValue != null) {
-			for ((k, v) in value) {
-				out.beginArray()
-				Starbound.gson.toJson(Starbound.gson.toJsonTree(k, boundKey) as JsonElement, out)
-				resolvedValue.write(out, v)
-				out.endArray()
-			}
-		} else {
-			for ((k, v) in value) {
-				out.beginArray()
-				Starbound.gson.toJson(Starbound.gson.toJsonTree(k, boundKey) as JsonElement, out)
-				Starbound.gson.toJson(Starbound.gson.toJsonTree(v, boundValue) as JsonElement, out)
-				out.endArray()
-			}
+		for ((k, v) in value) {
+			out.beginArray()
+			resolvedKey.write(out, k)
+			resolvedValue.write(out, v)
+			out.endArray()
 		}
 
 		out.endArray()
@@ -146,30 +101,10 @@ class MapAdapter<K, V>(private val boundKey: Class<K>, private val boundValue: C
 		val resolvedKey = resolvedKey
 		val resolvedValue = resolvedValue
 
-		if (resolvedKey != null && resolvedValue != null) {
-			while (reader.peek() != JsonToken.END_ARRAY) {
-				reader.beginArray()
-				builder.put(resolvedKey.read(reader), resolvedValue.read(reader))
-				reader.endArray()
-			}
-		} else if (resolvedKey != null) {
-			while (reader.peek() != JsonToken.END_ARRAY) {
-				reader.beginArray()
-				builder.put(resolvedKey.read(reader), Starbound.gson.fromJson(reader, boundValue))
-				reader.endArray()
-			}
-		} else if (resolvedValue != null) {
-			while (reader.peek() != JsonToken.END_ARRAY) {
-				reader.beginArray()
-				builder.put(Starbound.gson.fromJson(reader, boundKey), resolvedValue.read(reader))
-				reader.endArray()
-			}
-		} else {
-			while (reader.peek() != JsonToken.END_ARRAY) {
-				reader.beginArray()
-				builder.put(Starbound.gson.fromJson(reader, boundKey), Starbound.gson.fromJson(reader, boundValue))
-				reader.endArray()
-			}
+		while (reader.peek() != JsonToken.END_ARRAY) {
+			reader.beginArray()
+			builder.put(resolvedKey.read(reader), resolvedValue.read(reader))
+			reader.endArray()
 		}
 
 		reader.endArray()
@@ -188,16 +123,9 @@ class StringMapAdapter<V>(private val bound: Class<V>) : TypeAdapter<Map<String,
 
 		out.beginObject()
 
-		if (resolvedBound != null) {
-			for ((k, v) in value) {
-				out.name(k)
-				resolvedBound.write(out, v)
-			}
-		} else {
-			for ((k, v) in value) {
-				out.name(k)
-				Starbound.gson.toJson(Starbound.gson.toJsonTree(v, bound) as JsonElement, out)
-			}
+		for ((k, v) in value) {
+			out.name(k)
+			resolvedBound.write(out, v)
 		}
 
 		out.endObject()
@@ -210,14 +138,8 @@ class StringMapAdapter<V>(private val bound: Class<V>) : TypeAdapter<Map<String,
 
 		val resolvedBound = resolvedBound
 
-		if (resolvedBound != null) {
-			while (reader.peek() != JsonToken.END_OBJECT) {
-				builder.put(reader.nextName(), resolvedBound.read(reader))
-			}
-		} else {
-			while (reader.peek() != JsonToken.END_OBJECT) {
-				builder.put(reader.nextName(), Starbound.gson.fromJson(reader, bound))
-			}
+		while (reader.peek() != JsonToken.END_OBJECT) {
+			builder.put(reader.nextName(), resolvedBound.read(reader))
 		}
 
 		reader.endObject()
@@ -226,13 +148,30 @@ class StringMapAdapter<V>(private val bound: Class<V>) : TypeAdapter<Map<String,
 	}
 }
 
+private class LazyTypeProvider<T : Any?>(private val bound: Class<T>) : TypeAdapter<T>() {
+	private val resolved by lazy { resolveBound(bound) }
+
+	override fun write(out: JsonWriter, value: T) {
+		resolved.write(out, value)
+	}
+
+	override fun read(`in`: JsonReader): T {
+		return resolved.read(`in`)
+	}
+}
+
 /**
  * TypeAdapter для классов которые создаются единожды и более не меняются ("бетонных классов").
  */
 class KConcreteTypeAdapter<T : Any>(
 	val bound: KClass<T>,
-	val types: ImmutableList<Pair<KProperty1<T, *>, TypeAdapter<*>>>
+	val types: ImmutableList<Pair<KProperty1<T, *>, TypeAdapter<*>>>,
+	val asList: Boolean = false
 ) : TypeAdapter<T>() {
+	fun register(gsonBuilder: GsonBuilder) {
+		gsonBuilder.registerTypeAdapter(bound.java, this)
+	}
+
 	private val returnTypeCache = Object2ObjectArrayMap<KProperty1<T, *>, KType>()
 	private val mapped = Object2IntArrayMap<String>()
 
@@ -250,7 +189,7 @@ class KConcreteTypeAdapter<T : Any>(
 		}
 	}
 
-	private val factory: KFunction<T> = bound.constructors.firstOrNull first@{
+	private val regularFactory: KFunction<T> = bound.constructors.firstOrNull first@{
 		if (it.parameters.size == types.size) {
 			val iterator = types.iterator()
 
@@ -271,11 +210,50 @@ class KConcreteTypeAdapter<T : Any>(
 		return@first false
 	} ?: throw NoSuchElementException("Unable to determine constructor for ${bound.qualifiedName} matching (${types.joinToString(", ")})")
 
+	private val syntheticFactory: Constructor<T>? = try {
+		bound.java.getDeclaredConstructor(*types.map { (it.first.returnType.classifier as KClass<*>).java }.also {
+			it as MutableList
+			it.add(Int::class.java)
+			it.add(DefaultConstructorMarker::class.java)
+		}.toTypedArray())
+	} catch(_: NoSuchMethodException) {
+		null
+	}
+
+	private val syntheticPrimitives: Array<Any?>?
+
+	init {
+		if (syntheticFactory == null) {
+			syntheticPrimitives = null
+		} else {
+			syntheticPrimitives = arrayOfNulls(syntheticFactory.parameters.size)
+
+			for ((i, param) in syntheticFactory.parameters.withIndex()) {
+				val type = param.parameterizedType as? Class<*> ?: continue
+
+				if (type.isPrimitive) {
+					syntheticPrimitives[i] = when (type.name) {
+						"boolean" -> false
+						"int" -> 0
+						"long" -> 0L
+						"short" -> (0).toShort()
+						"byte" -> (0).toByte()
+						"char" -> (0).toChar()
+						"float" -> 0f
+						"double" -> 0.0
+						else -> throw IllegalArgumentException("mamma mia: ${type.name}")
+					}
+				}
+			}
+		}
+	}
+
 	override fun write(out: JsonWriter, value: T) {
 		out.beginObject()
 
 		for ((field, adapter) in types) {
 			out.name(field.name)
+			@Suppress("unchecked_cast")
 			(adapter as TypeAdapter<Any>).write(out, (field as KProperty1<T, Any>).get(value))
 		}
 
@@ -283,79 +261,211 @@ class KConcreteTypeAdapter<T : Any>(
 	}
 
 	override fun read(reader: JsonReader): T {
-		reader.beginObject()
+		if (asList) {
+			reader.beginArray()
+		} else {
+			reader.beginObject()
+		}
 
+		val presentValues = BooleanArray(types.size)
 		val readValues = arrayOfNulls<Any>(types.size)
 
-		while (reader.peek() != JsonToken.END_OBJECT) {
-			val name = reader.nextName()
-			val fieldId = mapped.getInt(name)
+		if (asList) {
+			val iterator = types.iterator()
+			var fieldId = 0
 
-			if (fieldId == -1) {
-				if (loggedMisses.add(name)) {
-					LOGGER.warn("Skipping JSON field with name $name because ${bound.qualifiedName} has no such field")
+			while (reader.peek() != JsonToken.END_ARRAY) {
+				if (!iterator.hasNext()) {
+					val name = fieldId.toString()
+
+					if (loggedMisses.add(name)) {
+						LOGGER.warn("Skipping JSON field with name $name because ${bound.qualifiedName} has no such field")
+					}
+
+					reader.skipValue()
+					fieldId++
+
+					continue
 				}
 
-				reader.skipValue()
-			} else {
-				val (field, adapter) = types[fieldId]
+				val (field, adapter) = iterator.next()
 
 				try {
 					readValues[fieldId] = adapter.read(reader)
+					presentValues[fieldId] = true
 				} catch(err: Throwable) {
 					throw JsonSyntaxException("Exception reading field ${field.name}", err)
 				}
+
+				fieldId++
+			}
+		} else {
+			while (reader.peek() != JsonToken.END_OBJECT) {
+				val name = reader.nextName()
+				val fieldId = mapped.getInt(name)
+
+				if (fieldId == -1) {
+					if (loggedMisses.add(name)) {
+						LOGGER.warn("Skipping JSON field with name $name because ${bound.qualifiedName} has no such field")
+					}
+
+					reader.skipValue()
+				} else {
+					val (field, adapter) = types[fieldId]
+
+					try {
+						readValues[fieldId] = adapter.read(reader)
+						presentValues[fieldId] = true
+					} catch(err: Throwable) {
+						throw JsonSyntaxException("Exception reading field ${field.name}", err)
+					}
+				}
 			}
 		}
 
-		for ((i, pair) in types.withIndex()) {
-			val (field) = pair
-
-			if (readValues[i] == null && !returnTypeCache[field]!!.isMarkedNullable) {
-				throw JsonSyntaxException("Field ${field.name} does not accept nulls")
+		for (i in readValues.indices) {
+			if (readValues[i] is String) {
+				readValues[i] = (readValues[i] as String).intern()
 			}
 		}
 
-		reader.endObject()
+		if (asList) {
+			reader.endArray()
+		} else {
+			reader.endObject()
+		}
 
-		return factory.call(*readValues as Array<out Any>)
+		if (syntheticFactory == null || presentValues.all { it }) {
+			for ((i, pair) in types.withIndex()) {
+				val (field) = pair
+
+				if (readValues[i] == null) {
+					if (!returnTypeCache[field]!!.isMarkedNullable) {
+						throw JsonSyntaxException("Field ${field.name} does not accept nulls")
+					}
+
+					if (!regularFactory.parameters[i].isOptional && !presentValues[i]) {
+						throw JsonSyntaxException("Field ${field.name} must be defined (even just as null)")
+					}
+				}
+			}
+
+			@Suppress("unchecked_cast")
+			return regularFactory.call(*readValues as Array<out Any>)
+		} else {
+			val ints = if (presentValues.size % 31 == 0) presentValues.size / 31 else presentValues.size / 31 + 1
+			val copied = readValues.copyOf(readValues.size + ints + 1)
+			var intIndex = readValues.size
+			var target = 0
+			var targetMove = 0
+
+			for (bool in presentValues) {
+				if (!bool) {
+					target = target.or(1.shl(targetMove))
+				}
+
+				targetMove++
+
+				if (targetMove >= 32) {
+					copied[intIndex++] = target
+					target = 0
+					targetMove = 0
+				}
+			}
+
+			if (targetMove != 0) {
+				copied[intIndex] = target
+			}
+
+			val syntheticPrimitives = syntheticPrimitives!!
+
+			for ((i, pair) in types.withIndex()) {
+				if (copied[i] != null) {
+					continue
+				}
+
+				val (field) = pair
+				val param = regularFactory.parameters[i]
+
+				if (!param.isOptional && !presentValues[i]) {
+					throw JsonSyntaxException("Field ${field.name} is missing")
+				}
+
+				if (returnTypeCache[field]!!.isMarkedNullable) {
+					continue
+				}
+
+				if (param.isOptional && !presentValues[i]) {
+					copied[i] = syntheticPrimitives[i] ?: throw NullPointerException("HOW $i")
+					continue
+				}
+
+				throw JsonSyntaxException("Field ${field.name} does not accept nulls")
+			}
+
+			return syntheticFactory.newInstance(*copied)
+		}
 	}
 
 	class Builder<T : Any>(val clazz: KClass<T>) {
 		private val types = ArrayList<Pair<KProperty1<T, *>, TypeAdapter<*>>>()
 
 		/**
-		 * Добавляет поле без generic типов
+		 * Добавляет поле с определённым адаптером
 		 */
-		fun plain(field: KProperty1<T, *>): Builder<T> {
-			val returnType = field.returnType
-			val classifier = returnType.classifier as? KClass<*> ?: throw ClassCastException("Unable to cast ${returnType.classifier} to KClass of property ${field.name}!")
+		fun <V> plain(field: KProperty1<T, V>, adapter: TypeAdapter<V>): Builder<T> {
+			types.add(field to adapter)
+			return this
+		}
 
-			if (classifier.isSuperclassOf(Float::class)) {
-				types.add(field to TypeAdapters.FLOAT)
-			} else if (classifier.isSuperclassOf(Double::class)) {
-				types.add(field to TypeAdapters.DOUBLE)
-			} else if (classifier.isSuperclassOf(Int::class)) {
-				types.add(field to TypeAdapters.INTEGER)
-			} else if (classifier.isSuperclassOf(Long::class)) {
-				types.add(field to TypeAdapters.LONG)
-			} else if (classifier.isSuperclassOf(String::class)) {
-				types.add(field to TypeAdapters.STRING)
-			} else if (classifier.isSuperclassOf(Boolean::class)) {
-				types.add(field to TypeAdapters.BOOLEAN)
-			} else {
-				types.add(field to PassthroughAdapter(classifier.java))
+		/**
+		 * Добавляет поле(я) без generic типов и без преобразователей
+		 */
+		fun plain(vararg fields: KProperty1<T, *>): Builder<T> {
+			for (field in fields) {
+				val returnType = field.returnType
+				val classifier = returnType.classifier as? KClass<*> ?: throw ClassCastException("Unable to cast ${returnType.classifier} to KClass of property ${field.name}!")
+
+				if (classifier.isSuperclassOf(Float::class)) {
+					types.add(field to TypeAdapters.FLOAT)
+				} else if (classifier.isSuperclassOf(Double::class)) {
+					types.add(field to TypeAdapters.DOUBLE)
+				} else if (classifier.isSuperclassOf(Int::class)) {
+					types.add(field to TypeAdapters.INTEGER)
+				} else if (classifier.isSuperclassOf(Long::class)) {
+					types.add(field to TypeAdapters.LONG)
+				} else if (classifier.isSuperclassOf(String::class)) {
+					types.add(field to TypeAdapters.STRING)
+				} else if (classifier.isSuperclassOf(Boolean::class)) {
+					types.add(field to TypeAdapters.BOOLEAN)
+				} else {
+					types.add(field to LazyTypeProvider(classifier.java))
+				}
 			}
 
 			return this
 		}
 
+		/**
+		 * Добавляет поле(я) без generic типов.
+		 *
+		 * Если поле с данным именем не найдено, кидается [NoSuchElementException]
+		 */
+		fun plain(vararg fields: String): Builder<T> {
+			val members = clazz.memberProperties
+
+			return this.plain(*Array(fields.size) {
+				val field = fields[it]
+				return@Array members.firstOrNull { it.name == field } ?: throw NoSuchElementException("Unable to find field $field")
+			})
+		}
+
 		/**
 		 * Добавляет поле, которое содержит список значений V (без null).
 		 *
 		 * Список неизменяем (создаётся объект [ImmutableList])
 		 */
-		fun <V> list(field: KProperty1<T, List<V>>, type: Class<V>): Builder<T> {
+		fun <V : Any> list(field: KProperty1<T, List<V>?>, type: Class<V>): Builder<T> {
 			types.add(field to ListAdapter(type))
 			return this
 		}
@@ -365,7 +475,7 @@ class KConcreteTypeAdapter<T : Any>(
 		 *
 		 * Список неизменяем (создаётся объект [ImmutableList])
 		 */
-		fun <V : Any> list(field: KProperty1<T, List<V>>, type: KClass<V>): Builder<T> {
+		fun <V : Any> list(field: KProperty1<T, List<V>?>, type: KClass<V>): Builder<T> {
 			return this.list(field, type.java)
 		}
 
@@ -379,6 +489,16 @@ class KConcreteTypeAdapter<T : Any>(
 			return this
 		}
 
+		/**
+		 * Добавляет поле-таблицу, которое кодируется как [[key, value], [key, value], ...]
+		 *
+		 * Таблица неизменяема (создаётся объект [ImmutableMap])
+		 */
+		fun <K : Any, V : Any> map(field: KProperty1<T, Map<K, V>>, keyType: KClass<K>, valueType: KClass<V>): Builder<T> {
+			types.add(field to MapAdapter(keyType.java, valueType.java))
+			return this
+		}
+
 		/**
 		 * Добавляет поле-таблицу, которое кодируется как {"a": value, "b": value, ...}
 		 *
@@ -389,8 +509,18 @@ class KConcreteTypeAdapter<T : Any>(
 			return this
 		}
 
-		fun build(): KConcreteTypeAdapter<T> {
-			return KConcreteTypeAdapter(clazz, ImmutableList.copyOf(types))
+		/**
+		 * Добавляет поле-таблицу, которое кодируется как {"a": value, "b": value, ...}
+		 *
+		 * Таблица неизменяема (создаётся объект [ImmutableMap])
+		 */
+		fun <V : Any> map(field: KProperty1<T, Map<String, V>>, valueType: KClass<V>): Builder<T> {
+			types.add(field to StringMapAdapter(valueType.java))
+			return this
+		}
+
+		fun build(asList: Boolean = false): KConcreteTypeAdapter<T> {
+			return KConcreteTypeAdapter(clazz, ImmutableList.copyOf(types), asList = asList)
 		}
 	}