From 66a3f0304a5a64a524c878a80ea25dc40b50e45a Mon Sep 17 00:00:00 2001 From: DBotThePony Date: Tue, 2 Apr 2024 21:53:18 +0700 Subject: [PATCH] Minor cleanup, VersionedAdapter --- .../ru/dbotthepony/kstarbound/Starbound.kt | 66 ++++++--- .../dbotthepony/kstarbound/VersionRegistry.kt | 31 ++++ .../ru/dbotthepony/kstarbound/defs/Damage.kt | 9 +- .../dbotthepony/kstarbound/defs/Drawable.kt | 2 + .../kstarbound/defs/actor/StatModifier.kt | 2 + .../kstarbound/defs/image/Image.kt | 53 +++++-- .../kstarbound/defs/item/InventoryIcon.kt | 2 +- .../kstarbound/defs/item/ItemDescriptor.kt | 35 +++-- .../defs/item/api/IArmorItemDefinition.kt | 2 +- .../defs/object/ObjectDefinition.kt | 6 +- .../defs/object/ObjectOrientation.kt | 23 ++- .../defs/quest/QuestArcDescriptor.kt | 15 +- .../kstarbound/defs/quest/QuestDescriptor.kt | 9 +- .../defs/world/VisitableWorldParameters.kt | 4 +- .../kstarbound/defs/world/WorldLayout.kt | 2 +- .../kstarbound/defs/world/WorldTemplate.kt | 7 +- .../kstarbound/json/NativeLegacy.kt | 2 +- .../kstarbound/json/VersionedAdapter.kt | 25 ++++ .../kstarbound/json/VersionedJson.kt | 17 +++ .../kstarbound/json/builder/FactoryAdapter.kt | 4 +- .../ru/dbotthepony/kstarbound/math/Line2d.kt | 53 +++---- .../kstarbound/server/ServerConnection.kt | 21 +-- .../server/world/ServerWorldTracker.kt | 5 +- .../kstarbound/world/UniversePos.kt | 137 ++++++++---------- 24 files changed, 345 insertions(+), 187 deletions(-) create mode 100644 src/main/kotlin/ru/dbotthepony/kstarbound/VersionRegistry.kt create mode 100644 src/main/kotlin/ru/dbotthepony/kstarbound/json/VersionedAdapter.kt diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/Starbound.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/Starbound.kt index e6293f33..5212f2d6 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/Starbound.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/Starbound.kt @@ -162,27 +162,67 @@ object Starbound : ISBFileLocator { // immeasurably lazy and fragile solution, too bad! // While having four separate Gson instances look like a (much) better solution (and it indeed could have been!), // we must not forget the fact that 'Starbound' and 'Consistent data format' are opposites, - // and there are cases of where discStore() calls toJson() on children data, despite it having discStore() too. - var IS_WRITING_LEGACY_JSON: Boolean by ThreadLocal.withInitial { false } + // and there are cases of where discStore() calls toJson() on children data, despite it having its own discStore() too. + var IS_LEGACY_JSON: Boolean by ThreadLocal.withInitial { false } private set - var IS_WRITING_STORE_JSON: Boolean by ThreadLocal.withInitial { false } + var IS_STORE_JSON: Boolean by ThreadLocal.withInitial { false } private set - fun writeLegacyJson(data: Any): JsonElement { + fun legacyJson(data: Any): JsonElement { try { - IS_WRITING_LEGACY_JSON = true + IS_LEGACY_JSON = true return gson.toJsonTree(data) } finally { - IS_WRITING_LEGACY_JSON = false + IS_LEGACY_JSON = false } } - fun writeLegacyJson(block: () -> T): T { + fun storeJson(data: Any): JsonElement { try { - IS_WRITING_LEGACY_JSON = true + IS_STORE_JSON = true + return gson.toJsonTree(data) + } finally { + IS_STORE_JSON = false + } + } + + fun legacyStoreJson(data: Any): JsonElement { + try { + IS_STORE_JSON = true + IS_LEGACY_JSON = true + return gson.toJsonTree(data) + } finally { + IS_STORE_JSON = false + IS_LEGACY_JSON = false + } + } + + fun legacyJson(block: () -> T): T { + try { + IS_LEGACY_JSON = true return block.invoke() } finally { - IS_WRITING_LEGACY_JSON = false + IS_LEGACY_JSON = false + } + } + + fun storeJson(block: () -> T): T { + try { + IS_STORE_JSON = true + return block.invoke() + } finally { + IS_STORE_JSON = false + } + } + + fun legacyStoreJson(block: () -> T): T { + try { + IS_STORE_JSON = true + IS_LEGACY_JSON = true + return block.invoke() + } finally { + IS_STORE_JSON = false + IS_LEGACY_JSON = false } } @@ -242,11 +282,6 @@ object Starbound : ISBFileLocator { registerTypeAdapter(RGBAColorTypeAdapter) - registerTypeAdapter(Drawable::Adapter) - registerTypeAdapter(ObjectOrientation::Adapter) - registerTypeAdapter(ObjectDefinition::Adapter) - registerTypeAdapter(StatModifier::Adapter) - registerTypeAdapterFactory(NativeLegacy.Companion) // математические классы @@ -261,8 +296,6 @@ object Starbound : ISBFileLocator { registerTypeAdapter(Vector4iTypeAdapter.nullSafe()) registerTypeAdapter(Vector4dTypeAdapter.nullSafe()) registerTypeAdapter(Vector4fTypeAdapter.nullSafe()) - registerTypeAdapterFactory(Line2d.Companion) - registerTypeAdapterFactory(UniversePos.Companion) registerTypeAdapterFactory(AbstractPerlinNoise.Companion) registerTypeAdapterFactory(WeightedList.Companion) @@ -291,7 +324,6 @@ object Starbound : ISBFileLocator { registerTypeAdapter(ItemStack.Adapter(this@Starbound)) - registerTypeAdapter(ItemDescriptor::Adapter) registerTypeAdapterFactory(TreasurePoolDefinition.Companion) registerTypeAdapterFactory(UniverseChunk.Companion) diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/VersionRegistry.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/VersionRegistry.kt new file mode 100644 index 00000000..17d9e0f7 --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/VersionRegistry.kt @@ -0,0 +1,31 @@ +package ru.dbotthepony.kstarbound + +import com.google.gson.JsonElement +import com.google.gson.TypeAdapter +import com.google.gson.stream.JsonReader +import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap +import ru.dbotthepony.kstarbound.json.VersionedJson + +object VersionRegistry { + private val versions = Object2IntOpenHashMap() + + fun currentVersion(name: String): Int { + return versions.getInt(name) + } + + fun make(name: String, contents: JsonElement): VersionedJson { + return VersionedJson(name, currentVersion(name), contents) + } + + fun load(name: String, reader: JsonReader, adapter: TypeAdapter): T { + val read = this.adapter.read(reader) ?: throw NullPointerException("Expected versioned json $name, but found null") + + if (read.version != currentVersion(name)) { + throw IllegalStateException("NYI: Migrating $name from ${read.version} to ${currentVersion(name)}") + } + + return adapter.fromJsonTree(read.content) + } + + private val adapter by lazy { Starbound.gson.getAdapter(VersionedJson::class.java) } +} diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/defs/Damage.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/defs/Damage.kt index 1ae5eb97..62ee0fef 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/defs/Damage.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/defs/Damage.kt @@ -31,6 +31,7 @@ import ru.dbotthepony.kstarbound.json.builder.FactoryAdapter import ru.dbotthepony.kstarbound.json.builder.IStringSerializable import ru.dbotthepony.kstarbound.json.builder.JsonFactory import ru.dbotthepony.kstarbound.json.getAdapter +import ru.dbotthepony.kstarbound.math.Line2d import ru.dbotthepony.kstarbound.network.syncher.legacyCodec import ru.dbotthepony.kstarbound.network.syncher.nativeCodec import ru.dbotthepony.kstarbound.world.physics.Poly @@ -111,7 +112,7 @@ data class TouchDamage( @JsonAdapter(DamageSource.Adapter::class) data class DamageSource( val damageType: DamageType, - val damageArea: Either>, + val damageArea: Either, val damage: Double, val trackSourceEntity: Boolean, val sourceEntityId: Int = 0, @@ -125,7 +126,7 @@ data class DamageSource( ) { constructor(stream: DataInputStream, isLegacy: Boolean) : this( DamageType.entries[stream.readUnsignedByte()], - stream.readMVariant2({ Poly.read(this, isLegacy) }, { stream.readVector2d(isLegacy) to stream.readVector2d(isLegacy) }) ?: throw IllegalArgumentException("Empty MVariant damageArea"), + stream.readMVariant2({ Poly.read(this, isLegacy) }, { Line2d(stream, isLegacy) }) ?: throw IllegalArgumentException("Empty MVariant damageArea"), stream.readDouble(isLegacy), stream.readBoolean(), stream.readInt(), @@ -140,7 +141,7 @@ data class DamageSource( data class JsonData( val poly: Poly? = null, - val line: Pair? = null, + val line: Line2d? = null, val damage: Double, val damageType: DamageType = DamageType.DAMAGE, val trackSourceEntity: Boolean = true, @@ -158,7 +159,7 @@ data class DamageSource( fun write(stream: DataOutputStream, isLegacy: Boolean) { stream.writeByte(damageType.ordinal) - stream.writeMVariant2(damageArea, { it.write(stream, isLegacy) }, { stream.writeStruct2d(it.first, isLegacy); stream.writeStruct2d(it.second, isLegacy) }) + stream.writeMVariant2(damageArea, { it.write(stream, isLegacy) }, { it.write(stream, isLegacy) }) stream.writeDouble(damage, isLegacy) stream.writeBoolean(trackSourceEntity) stream.writeInt(sourceEntityId) diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/defs/Drawable.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/defs/Drawable.kt index e7bc8d4b..d3c0c22a 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/defs/Drawable.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/defs/Drawable.kt @@ -4,6 +4,7 @@ import com.google.common.collect.ImmutableList import com.google.gson.Gson import com.google.gson.JsonObject import com.google.gson.TypeAdapter +import com.google.gson.annotations.JsonAdapter import com.google.gson.reflect.TypeToken import com.google.gson.stream.JsonReader import com.google.gson.stream.JsonWriter @@ -23,6 +24,7 @@ import ru.dbotthepony.kommons.gson.consumeNull import ru.dbotthepony.kommons.gson.contains import ru.dbotthepony.kstarbound.math.Line2d +@JsonAdapter(Drawable.Adapter::class) sealed class Drawable(val position: Vector2f, val color: RGBAColor, val fullbright: Boolean) { @JsonFactory data class Transformations(val centered: Boolean = false, val rotation: Float = 0f, val mirrored: Boolean = false, val scale: Either = Either.left(1f)) diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/defs/actor/StatModifier.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/defs/actor/StatModifier.kt index 124d4155..9665f959 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/defs/actor/StatModifier.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/defs/actor/StatModifier.kt @@ -4,6 +4,7 @@ import com.google.gson.Gson import com.google.gson.JsonObject import com.google.gson.JsonSyntaxException import com.google.gson.TypeAdapter +import com.google.gson.annotations.JsonAdapter import com.google.gson.stream.JsonReader import com.google.gson.stream.JsonWriter import ru.dbotthepony.kommons.gson.consumeNull @@ -17,6 +18,7 @@ import ru.dbotthepony.kstarbound.network.syncher.nativeCodec import java.io.DataInputStream import java.io.DataOutputStream +@JsonAdapter(StatModifier.Adapter::class) data class StatModifier(val stat: String, val value: Double, val type: StatModifierType) { class Adapter(gson: Gson) : TypeAdapter() { private val objects = gson.getAdapter(JsonObject::class.java) diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/defs/image/Image.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/defs/image/Image.kt index bcda5592..38090874 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/defs/image/Image.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/defs/image/Image.kt @@ -53,7 +53,7 @@ class Image private constructor( val width: Int, val height: Int, val amountOfChannels: Int, - sprites: List? + spritesData: Pair, IStarboundFile>? ) { init { check(width >= 0) { "Invalid width $width" } @@ -61,22 +61,45 @@ class Image private constructor( check(amountOfChannels in 1 .. 4) { "Unknown number of channels $amountOfChannels" } } - private val spritesInternal = Object2ObjectLinkedOpenHashMap() + private val spritesInternal = LinkedHashMap() private var dataRef: WeakReference? = null private val lock = ReentrantLock() //private val _texture = ThreadLocal>() init { - if (sprites == null) { + if (spritesData == null) { this.spritesInternal["default"] = Sprite("default", 0, 0, width, height) } else { + val (sprites, origin) = spritesData + for (data in sprites) { - this.spritesInternal[data.name] = Sprite( - data.name, - data.coordinates.x, - data.coordinates.y, - data.coordinates.z - data.coordinates.x, - data.coordinates.w - data.coordinates.y) + var sX = data.coordinates.x % width + var sY = data.coordinates.y % height + + if (sX !in 0 .. width) { + //LOGGER.warn("Sprite X offset ${data.name} is out of bounds: $sX, clamping to 0 .. $width. (image: $source; frames: $origin)") + sX = sX.coerceIn(0, width) + } + + if (sY !in 0 .. height) { + //LOGGER.warn("Sprite Y offset ${data.name} is out of bounds: $sY, clamping to 0 .. $height. (image: $source; frames: $origin)") + sY = sY.coerceIn(0, height) + } + + var sWidth = data.coordinates.z - sX + var sHeight = data.coordinates.w - sY + + if (sWidth !in 0 .. width) { + //LOGGER.warn("Sprite width ${data.name} is out of bounds: $sWidth, clamping to 0 .. $width. (image: $source; frames: $origin)") + sWidth = sWidth.coerceIn(0, width) + } + + if (sHeight !in 0 .. height) { + //LOGGER.warn("Sprite height ${data.name} is out of bounds: $sHeight, clamping to 0 .. $height. (image: $source; frames: $origin)") + sHeight = sHeight.coerceIn(0, height) + } + + this.spritesInternal[data.name] = Sprite(data.name, sX, sY, sWidth, sHeight) } } } @@ -273,7 +296,7 @@ class Image private constructor( for (y in 0 until PIXELS_IN_STARBOUND_UNITi) { val ypixel = (yspace * PIXELS_IN_STARBOUND_UNITi + y - pixelOffset.y) - if (ypixel !in 0 until width) + if (ypixel !in 0 until height) continue for (x in 0 until PIXELS_IN_STARBOUND_UNITi) { @@ -300,12 +323,12 @@ class Image private constructor( } companion object : TypeAdapter() { - const val FILL_RATIO = 1 / (PIXELS_IN_STARBOUND_UNIT * PIXELS_IN_STARBOUND_UNIT) + private val LOGGER = LogManager.getLogger() private val objects by lazy { Starbound.gson.getAdapter(JsonObject::class.java) } private val vectors by lazy { Starbound.gson.getAdapter(Vector4i::class.java) } private val vectors2 by lazy { Starbound.gson.getAdapter(Vector2i::class.java) } - private val configCache = ConcurrentHashMap>>() + private val configCache = ConcurrentHashMap, IStarboundFile>>>() private val imageCache = ConcurrentHashMap>() private val logger = LogManager.getLogger() @@ -494,17 +517,17 @@ class Image private constructor( return ImmutableList.copyOf(sprites.values) } - private fun compute(it: String): Optional> { + private fun compute(it: String): Optional, IStarboundFile>> { val find = Starbound.locate("$it.frames") if (!find.exists) { return Optional.empty() } else { - return Optional.of(parseFrames(objects.read(JsonReader(find.reader()).also { it.isLenient = true }))) + return Optional.of(parseFrames(objects.read(JsonReader(find.reader()).also { it.isLenient = true })) to find) } } - private fun getConfig(path: String): List? { + private fun getConfig(path: String): Pair, IStarboundFile>? { var folder = path.substringBefore(':').substringBeforeLast('/') val name = path.substringBefore(':').substringAfterLast('/').substringBefore('.') diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/defs/item/InventoryIcon.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/defs/item/InventoryIcon.kt index 45f58c27..807c0ca9 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/defs/item/InventoryIcon.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/defs/item/InventoryIcon.kt @@ -17,7 +17,7 @@ data class InventoryIcon( override val image: SpriteReference ) : IInventoryIcon { companion object : TypeAdapter() { - private val adapter by lazy { FactoryAdapter.createFor(InventoryIcon::class, JsonFactory(), Starbound.gson) } + private val adapter by lazy { FactoryAdapter.createFor(InventoryIcon::class, Starbound.gson) } private val images by lazy { Starbound.gson.getAdapter(SpriteReference::class.java) } override fun write(out: JsonWriter, value: InventoryIcon?) { diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/defs/item/ItemDescriptor.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/defs/item/ItemDescriptor.kt index 7946cc2f..0ed1286b 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/defs/item/ItemDescriptor.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/defs/item/ItemDescriptor.kt @@ -8,6 +8,7 @@ import com.google.gson.JsonObject import com.google.gson.JsonPrimitive import com.google.gson.JsonSyntaxException import com.google.gson.TypeAdapter +import com.google.gson.annotations.JsonAdapter import com.google.gson.stream.JsonReader import com.google.gson.stream.JsonWriter import org.classdump.luna.ByteString @@ -28,6 +29,8 @@ import ru.dbotthepony.kommons.io.writeBinaryString import ru.dbotthepony.kommons.io.writeVarLong import ru.dbotthepony.kstarbound.Registries import ru.dbotthepony.kstarbound.Registry +import ru.dbotthepony.kstarbound.Starbound +import ru.dbotthepony.kstarbound.VersionRegistry import ru.dbotthepony.kstarbound.defs.item.api.IItemDefinition import ru.dbotthepony.kstarbound.io.readInternedString import ru.dbotthepony.kstarbound.item.ItemStack @@ -105,6 +108,7 @@ fun ItemDescriptor(stream: DataInputStream): ItemDescriptor { * [parameters] is considered to be immutable and should not be modified * directly (must be copied for mutable context) */ +@JsonAdapter(ItemDescriptor.Adapter::class) data class ItemDescriptor( val name: String, val count: Long, @@ -123,15 +127,21 @@ data class ItemDescriptor( return ItemStack.create(this) } - fun toJson(): JsonObject? { - if (isEmpty) { - return null - } + private fun toJsonStruct() = JsonObject().also { + it.add("name", JsonPrimitive(name)) + it.add("count", JsonPrimitive(count)) + it.add("parameters", parameters.deepCopy()) + } - return JsonObject().also { - it.add("name", JsonPrimitive(name)) - it.add("count", JsonPrimitive(count)) - it.add("parameters", parameters.deepCopy()) + fun toJson(): JsonObject? { + if (Starbound.IS_STORE_JSON) { + return VersionRegistry.make("Item", toJsonStruct()).toJson() + } else { + if (isEmpty) { + return null + } + + return toJsonStruct() } } @@ -156,19 +166,14 @@ data class ItemDescriptor( class Adapter(gson: Gson) : TypeAdapter() { private val elements = gson.getAdapter(JsonElement::class.java) - override fun write(out: JsonWriter, value: ItemDescriptor?) { - if (value == null) - out.nullValue() - else if (value.isEmpty) + override fun write(out: JsonWriter, value: ItemDescriptor) { + if (value.isEmpty) out.nullValue() else out.value(value.toJson()) } override fun read(`in`: JsonReader): ItemDescriptor { - if (`in`.consumeNull()) - return EMPTY - return ItemDescriptor(elements.read(`in`)) } } diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/defs/item/api/IArmorItemDefinition.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/defs/item/api/IArmorItemDefinition.kt index 32f41bda..6ea73ef5 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/defs/item/api/IArmorItemDefinition.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/defs/item/api/IArmorItemDefinition.kt @@ -45,7 +45,7 @@ interface IArmorItemDefinition : ILeveledItemDefinition, IScriptableItemDefiniti override fun create(gson: Gson, type: TypeToken): TypeAdapter? { if (type.rawType == Frames::class.java) { return object : TypeAdapter() { - private val adapter = FactoryAdapter.createFor(Frames::class, JsonFactory(), gson) + private val adapter = FactoryAdapter.createFor(Frames::class, gson) private val frames = gson.getAdapter(SpriteReference::class.java) diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/defs/object/ObjectDefinition.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/defs/object/ObjectDefinition.kt index a8c083fb..8b63ced3 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/defs/object/ObjectDefinition.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/defs/object/ObjectDefinition.kt @@ -9,6 +9,7 @@ import com.google.gson.JsonObject import com.google.gson.JsonPrimitive import com.google.gson.JsonSyntaxException import com.google.gson.TypeAdapter +import com.google.gson.annotations.JsonAdapter import com.google.gson.stream.JsonReader import com.google.gson.stream.JsonWriter import ru.dbotthepony.kommons.util.Either @@ -32,6 +33,7 @@ import ru.dbotthepony.kstarbound.defs.AssetReference import ru.dbotthepony.kstarbound.defs.animation.AnimationDefinition import ru.dbotthepony.kstarbound.defs.item.ItemDescriptor +@JsonAdapter(ObjectDefinition.Adapter::class) data class ObjectDefinition( val objectName: String, val objectType: ObjectType = ObjectType.OBJECT, @@ -79,10 +81,6 @@ data class ObjectDefinition( val flickerPeriod: PeriodicFunction? = null, val orientations: ImmutableList, ) { - companion object { - - } - class Adapter(gson: Gson) : TypeAdapter() { @JsonFactory(logMisses = false) data class PlainData( diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/defs/object/ObjectOrientation.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/defs/object/ObjectOrientation.kt index b61fffbc..29501f66 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/defs/object/ObjectOrientation.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/defs/object/ObjectOrientation.kt @@ -7,6 +7,7 @@ import com.google.gson.JsonArray import com.google.gson.JsonObject import com.google.gson.JsonSyntaxException import com.google.gson.TypeAdapter +import com.google.gson.annotations.JsonAdapter import com.google.gson.reflect.TypeToken import com.google.gson.stream.JsonReader import com.google.gson.stream.JsonWriter @@ -33,6 +34,7 @@ import ru.dbotthepony.kstarbound.defs.tile.TileDefinition import ru.dbotthepony.kstarbound.world.Side import kotlin.math.PI +@JsonAdapter(ObjectOrientation.Adapter::class) data class ObjectOrientation( val json: JsonObject, val flipImages: Boolean = false, @@ -173,10 +175,23 @@ data class ObjectOrientation( } } - val minX = occupySpaces.minOf { it.x } - val minY = occupySpaces.minOf { it.y } - val maxX = occupySpaces.maxOf { it.x } - val maxY = occupySpaces.maxOf { it.y } + val minX: Int + val minY: Int + val maxX: Int + val maxY: Int + + if (occupySpaces.isNotEmpty()) { + minX = occupySpaces.minOf { it.x } + minY = occupySpaces.minOf { it.y } + maxX = occupySpaces.maxOf { it.x } + maxY = occupySpaces.maxOf { it.y } + } else { + minX = 0 + minY = 0 + maxX = 0 + maxY = 0 + } + val boundingBox = AABBi(Vector2i(minX, minY), Vector2i(maxX, maxY)) val metaBoundBox = obj["metaBoundBox"]?.let { aabbs.fromJsonTree(it) } diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/defs/quest/QuestArcDescriptor.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/defs/quest/QuestArcDescriptor.kt index a5e9f0f9..edcb080c 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/defs/quest/QuestArcDescriptor.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/defs/quest/QuestArcDescriptor.kt @@ -1,17 +1,28 @@ package ru.dbotthepony.kstarbound.defs.quest import com.google.common.collect.ImmutableList +import com.google.gson.Gson +import com.google.gson.TypeAdapter +import com.google.gson.annotations.JsonAdapter +import com.google.gson.stream.JsonReader +import com.google.gson.stream.JsonWriter import ru.dbotthepony.kommons.io.readCollection import ru.dbotthepony.kommons.io.writeBinaryString import ru.dbotthepony.kommons.io.writeCollection +import ru.dbotthepony.kstarbound.Starbound +import ru.dbotthepony.kstarbound.VersionRegistry import ru.dbotthepony.kstarbound.io.readInternedString +import ru.dbotthepony.kstarbound.json.VersionedAdapter +import ru.dbotthepony.kstarbound.json.VersionedJson +import ru.dbotthepony.kstarbound.json.builder.FactoryAdapter import ru.dbotthepony.kstarbound.json.builder.JsonFactory +import ru.dbotthepony.kstarbound.json.getAdapter import ru.dbotthepony.kstarbound.network.syncher.legacyCodec import ru.dbotthepony.kstarbound.network.syncher.nativeCodec import java.io.DataInputStream import java.io.DataOutputStream -@JsonFactory +@JsonAdapter(QuestArcDescriptor.Adapter::class) data class QuestArcDescriptor( val quests: ImmutableList = ImmutableList.of(), val stagehandUniqueId: String? = null, @@ -27,6 +38,8 @@ data class QuestArcDescriptor( if (stagehandUniqueId != null) stream.writeBinaryString(stagehandUniqueId) } + class Adapter(gson: Gson) : VersionedAdapter("QuestArcDescriptor", FactoryAdapter.createFor(QuestArcDescriptor::class, gson)) + companion object { val CODEC = nativeCodec(::QuestArcDescriptor, QuestArcDescriptor::write) val LEGACY_CODEC = legacyCodec(::QuestArcDescriptor, QuestArcDescriptor::write) diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/defs/quest/QuestDescriptor.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/defs/quest/QuestDescriptor.kt index 352369ab..830359c4 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/defs/quest/QuestDescriptor.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/defs/quest/QuestDescriptor.kt @@ -1,18 +1,23 @@ package ru.dbotthepony.kstarbound.defs.quest import com.google.common.collect.ImmutableMap +import com.google.gson.Gson import com.google.gson.JsonObject +import com.google.gson.TypeAdapter +import com.google.gson.annotations.JsonAdapter import ru.dbotthepony.kommons.io.readMap import ru.dbotthepony.kommons.io.writeBinaryString import ru.dbotthepony.kommons.io.writeMap import ru.dbotthepony.kstarbound.io.readInternedString +import ru.dbotthepony.kstarbound.json.VersionedAdapter +import ru.dbotthepony.kstarbound.json.builder.FactoryAdapter import ru.dbotthepony.kstarbound.json.builder.JsonFactory import ru.dbotthepony.kstarbound.network.syncher.legacyCodec import ru.dbotthepony.kstarbound.network.syncher.nativeCodec import java.io.DataInputStream import java.io.DataOutputStream -@JsonFactory +@JsonAdapter(QuestDescriptor.Adapter::class) data class QuestDescriptor( val questId: String, val templateId: String = questId, @@ -33,6 +38,8 @@ data class QuestDescriptor( stream.writeLong(seed) } + class Adapter(gson: Gson) : VersionedAdapter("QuestDescriptor", FactoryAdapter.createFor(QuestDescriptor::class, gson)) + companion object { val CODEC = nativeCodec(::QuestDescriptor, QuestDescriptor::write) val LEGACY_CODEC = legacyCodec(::QuestDescriptor, QuestDescriptor::write) diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/defs/world/VisitableWorldParameters.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/defs/world/VisitableWorldParameters.kt index 305b974c..09ce5fb0 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/defs/world/VisitableWorldParameters.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/defs/world/VisitableWorldParameters.kt @@ -140,7 +140,7 @@ abstract class VisitableWorldParameters { this.weatherPool = read.weatherPool } - open fun toJson(data: JsonObject, isLegacy: Boolean = Starbound.IS_WRITING_LEGACY_JSON) { + open fun toJson(data: JsonObject, isLegacy: Boolean = Starbound.IS_LEGACY_JSON) { val store = StoreData( threatLevel, typeName, @@ -164,7 +164,7 @@ abstract class VisitableWorldParameters { data["type"] = type.jsonName } - fun toJson(isLegacy: Boolean = Starbound.IS_WRITING_LEGACY_JSON): JsonObject { + fun toJson(isLegacy: Boolean = Starbound.IS_LEGACY_JSON): JsonObject { val data = JsonObject() toJson(data, isLegacy) return data diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/defs/world/WorldLayout.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/defs/world/WorldLayout.kt index 1d8c5732..42217aa8 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/defs/world/WorldLayout.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/defs/world/WorldLayout.kt @@ -211,7 +211,7 @@ class WorldLayout { return Starbound.gson.toJsonTree(SerializedForm( worldSize, regionBlending, blockNoise, blendNoise, playerStartSearchRegions, biomes.list, terrainSelectors.list, - layers = layers.stream().map { it.toJson(Starbound.IS_WRITING_LEGACY_JSON) }.collect(JsonArrayCollector) + layers = layers.stream().map { it.toJson(Starbound.IS_LEGACY_JSON) }.collect(JsonArrayCollector) )) as JsonObject } diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/defs/world/WorldTemplate.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/defs/world/WorldTemplate.kt index aa5536de..2175e1b2 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/defs/world/WorldTemplate.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/defs/world/WorldTemplate.kt @@ -1,18 +1,13 @@ package ru.dbotthepony.kstarbound.defs.world -import com.google.gson.JsonElement -import com.google.gson.JsonNull import com.google.gson.JsonObject -import ru.dbotthepony.kommons.gson.set import ru.dbotthepony.kommons.util.Either import ru.dbotthepony.kommons.vector.Vector2i import ru.dbotthepony.kstarbound.Starbound -import ru.dbotthepony.kstarbound.fromJson import ru.dbotthepony.kstarbound.json.builder.JsonFactory import ru.dbotthepony.kstarbound.world.Universe import ru.dbotthepony.kstarbound.world.UniversePos import ru.dbotthepony.kstarbound.world.WorldGeometry -import kotlin.properties.Delegates class WorldTemplate(val geometry: WorldGeometry) { var seed: Long = 0L @@ -58,7 +53,7 @@ class WorldTemplate(val geometry: WorldGeometry) { fun toJson(): JsonObject { val data = Starbound.gson.toJsonTree(SerializedForm( celestialParameters, worldParameters, skyParameters, seed, - if (Starbound.IS_WRITING_LEGACY_JSON) Either.right(geometry.size) else Either.left(geometry), + if (Starbound.IS_LEGACY_JSON) Either.right(geometry.size) else Either.left(geometry), worldLayout )) as JsonObject diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/json/NativeLegacy.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/json/NativeLegacy.kt index e57eea7f..26add45e 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/json/NativeLegacy.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/json/NativeLegacy.kt @@ -106,7 +106,7 @@ abstract class NativeLegacy { override fun write(out: JsonWriter, value: NativeLegacy?) { if (value == null) out.nullValue() - else if (Starbound.IS_WRITING_LEGACY_JSON || value.nativeValue.isEmpty) + else if (Starbound.IS_LEGACY_JSON || value.nativeValue.isEmpty) right.write(out, value.legacy) else left.write(out, value.native) diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/json/VersionedAdapter.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/json/VersionedAdapter.kt new file mode 100644 index 00000000..84682443 --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/json/VersionedAdapter.kt @@ -0,0 +1,25 @@ +package ru.dbotthepony.kstarbound.json + +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.VersionRegistry + +abstract class VersionedAdapter(val name: String, val parent: TypeAdapter) : TypeAdapter() { + override fun write(out: JsonWriter, value: T) { + if (Starbound.IS_STORE_JSON) { + VersionRegistry.make(name, parent.toJsonTree(value)).toJson(out) + } else { + parent.write(out, value) + } + } + + override fun read(`in`: JsonReader): T { + if (Starbound.IS_STORE_JSON) { + return VersionRegistry.load(name, `in`, parent) + } else { + return parent.read(`in`) + } + } +} diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/json/VersionedJson.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/json/VersionedJson.kt index f605ac30..9bf9ccf9 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/json/VersionedJson.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/json/VersionedJson.kt @@ -1,12 +1,29 @@ package ru.dbotthepony.kstarbound.json import com.google.gson.JsonElement +import com.google.gson.JsonObject +import com.google.gson.stream.JsonWriter import ru.dbotthepony.kommons.io.readBinaryString +import ru.dbotthepony.kstarbound.Starbound +import ru.dbotthepony.kstarbound.json.builder.JsonFactory import java.io.DataInputStream +@JsonFactory data class VersionedJson(val identifier: String, val version: Int?, val content: JsonElement) { constructor(data: DataInputStream) : this( data.readBinaryString(), data.read().let { if (it > 0) data.readInt() else null }, data.readJsonElement()) + + fun toJson(): JsonObject { + return adapter.toJsonTree(this) as JsonObject + } + + fun toJson(writer: JsonWriter) { + adapter.write(writer, this) + } + + companion object { + private val adapter by lazy { Starbound.gson.getAdapter() } + } } diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/json/builder/FactoryAdapter.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/json/builder/FactoryAdapter.kt index a2c2446f..dc967cf6 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/json/builder/FactoryAdapter.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/json/builder/FactoryAdapter.kt @@ -484,7 +484,7 @@ class FactoryAdapter private constructor( companion object { private val LOGGER = LogManager.getLogger() - fun createFor(kclass: KClass, config: JsonFactory = JsonFactory(), gson: Gson, stringInterner: Interner = Starbound.STRINGS): TypeAdapter { + fun createFor(kclass: KClass, gson: Gson, config: JsonFactory = JsonFactory(), stringInterner: Interner = Starbound.STRINGS): TypeAdapter { val builder = Builder(kclass) val properties = kclass.declaredMembers.filterIsInstance>() @@ -537,7 +537,7 @@ class FactoryAdapter private constructor( val bconfig = first[0] as JsonFactory val kclass = raw.kotlin as KClass - return createFor(kclass, bconfig, gson, stringInterner) + return createFor(kclass, gson, bconfig, stringInterner) } return null diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/math/Line2d.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/math/Line2d.kt index 8c0ac1ce..ea5cc062 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/math/Line2d.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/math/Line2d.kt @@ -3,12 +3,19 @@ package ru.dbotthepony.kstarbound.math import com.google.gson.Gson import com.google.gson.TypeAdapter import com.google.gson.TypeAdapterFactory +import com.google.gson.annotations.JsonAdapter import com.google.gson.reflect.TypeToken import com.google.gson.stream.JsonReader import com.google.gson.stream.JsonWriter import ru.dbotthepony.kommons.gson.consumeNull import ru.dbotthepony.kommons.util.KOptional import ru.dbotthepony.kommons.vector.Vector2d +import ru.dbotthepony.kstarbound.io.readVector2d +import ru.dbotthepony.kstarbound.io.writeDouble +import ru.dbotthepony.kstarbound.io.writeStruct2d +import ru.dbotthepony.kstarbound.json.getAdapter +import java.io.DataInputStream +import java.io.DataOutputStream import kotlin.math.absoluteValue private operator fun Vector2d.compareTo(other: Vector2d): Int { @@ -17,7 +24,10 @@ private operator fun Vector2d.compareTo(other: Vector2d): Int { return cmp } +@JsonAdapter(Line2d.Adapter::class) data class Line2d(val a: Vector2d, val b: Vector2d) { + constructor(stream: DataInputStream, isLegacy: Boolean) : this(stream.readVector2d(isLegacy), stream.readVector2d(isLegacy)) + data class Intersection(val intersects: Boolean, val point: KOptional, val t: KOptional, val coincides: Boolean, val glances: Boolean) { companion object { val EMPTY = Intersection(false, KOptional(), KOptional(), false, false) @@ -32,6 +42,11 @@ data class Line2d(val a: Vector2d, val b: Vector2d) { return Line2d(b, a) } + fun write(stream: DataOutputStream, isLegacy: Boolean) { + stream.writeStruct2d(a, isLegacy) + stream.writeStruct2d(b, isLegacy) + } + // original source of this intersection algorithm: // https://stackoverflow.com/questions/563198/how-do-you-detect-where-two-line-segments-intersect // article "Intersection of two lines in three-space" by Ronald Goldman, published in Graphics Gems, page 304 @@ -110,33 +125,21 @@ data class Line2d(val a: Vector2d, val b: Vector2d) { return (other - a + difference() * proj).length } - companion object : TypeAdapterFactory { - const val NEAR_ZERO = Double.MIN_VALUE * 2.0 - const val NEAR_ONE = 1.0 - NEAR_ZERO + class Adapter(gson: Gson) : TypeAdapter() { + private val pair = gson.getAdapter>() - override fun create(gson: Gson, type: TypeToken): TypeAdapter? { - if (type.rawType == Line2d::class.java) { - val pair = gson.getAdapter(TypeToken.getParameterized(Vector2d::class.java, Vector2d::class.java)) as TypeAdapter> + override fun write(out: JsonWriter, value: Line2d) { + pair.write(out, value.a to value.b) + } - return object : TypeAdapter() { - override fun write(out: JsonWriter, value: Line2d?) { - if (value == null) - out.nullValue() - else - pair.write(out, value.a to value.b) - } - - override fun read(`in`: JsonReader): Line2d? { - if (`in`.consumeNull()) - return null - - val (a, b) = pair.read(`in`) - return Line2d(a, b) - } - } as TypeAdapter - } - - return null + override fun read(`in`: JsonReader): Line2d { + val (a, b) = pair.read(`in`) + return Line2d(a, b) } } + + companion object { + const val NEAR_ZERO = Double.MIN_VALUE * 2.0 + const val NEAR_ONE = 1.0 - NEAR_ZERO + } } diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/server/ServerConnection.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/server/ServerConnection.kt index f83db693..8c069b8c 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/server/ServerConnection.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/server/ServerConnection.kt @@ -145,17 +145,18 @@ class ServerConnection(val server: StarboundServer, type: ConnectionType) : Conn if (world == null) { send(PlayerWarpResultPacket(false, request, false)) - } else { - try { - world.acceptClient(this, request).await() - } catch (err: Throwable) { - send(PlayerWarpResultPacket(false, request, false)) + continue + } - if (world == shipWorld) { - disconnect("ShipWorld refused to accept its owner: $err") - } else { - enqueueWarp(returnWarp ?: WarpAlias.OwnShip) - } + try { + world.acceptClient(this, request).await() + } catch (err: Throwable) { + send(PlayerWarpResultPacket(false, request, false)) + + if (world == shipWorld) { + disconnect("ShipWorld refused to accept its owner: $err") + } else { + enqueueWarp(returnWarp ?: WarpAlias.OwnShip) } } } diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/server/world/ServerWorldTracker.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/server/world/ServerWorldTracker.kt index bf440f16..e0223f3a 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/server/world/ServerWorldTracker.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/server/world/ServerWorldTracker.kt @@ -33,7 +33,6 @@ import ru.dbotthepony.kstarbound.world.TileHealth import ru.dbotthepony.kstarbound.world.api.ImmutableCell import ru.dbotthepony.kstarbound.world.entities.AbstractEntity import ru.dbotthepony.kstarbound.world.entities.player.PlayerEntity -import ru.dbotthepony.kstarbound.world.entities.tile.WorldObject import java.io.DataOutputStream import java.util.HashMap import java.util.concurrent.ConcurrentLinkedQueue @@ -91,7 +90,7 @@ class ServerWorldTracker(val world: ServerWorld, val client: ServerConnection, p if (client.isLegacy) { client.send(WorldStartPacket( - templateData = Starbound.writeLegacyJson { world.template.toJson() }, + templateData = Starbound.legacyJson { world.template.toJson() }, skyData = skyData.toByteArray(), weatherData = ByteArray(0), playerStart = playerStart, @@ -105,7 +104,7 @@ class ServerWorldTracker(val world: ServerWorld, val client: ServerConnection, p localInterpolationMode = false, )) - Starbound.writeLegacyJson { + Starbound.legacyJson { client.sendAndFlush(CentralStructureUpdatePacket(Starbound.gson.toJsonTree(world.centralStructure))) } } diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/world/UniversePos.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/world/UniversePos.kt index 3706ca9b..9c06eccb 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/world/UniversePos.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/world/UniversePos.kt @@ -5,6 +5,7 @@ import com.google.gson.JsonObject import com.google.gson.JsonSyntaxException import com.google.gson.TypeAdapter import com.google.gson.TypeAdapterFactory +import com.google.gson.annotations.JsonAdapter import com.google.gson.reflect.TypeToken import com.google.gson.stream.JsonReader import com.google.gson.stream.JsonToken @@ -32,6 +33,7 @@ import java.io.DataOutputStream * No validity checking is done here, any coordinate to any body whether it * exists in a specific universe or not can be expressed. */ +@JsonAdapter(UniversePos.Adapter::class) data class UniversePos(val location: Vector3i = Vector3i.ZERO, val planetOrbit: Int = 0, val satelliteOrbit: Int = 0) { constructor(stream: DataInputStream, isLegacy: Boolean) : this(stream.readVector3i(), if (isLegacy) stream.readInt() else stream.readVarInt(), if (isLegacy) stream.readInt() else stream.readVarInt()) @@ -95,84 +97,71 @@ data class UniversePos(val location: Vector3i = Vector3i.ZERO, val planetOrbit: } } - companion object : TypeAdapterFactory { + class Adapter(gson: Gson) : TypeAdapter() { + private val vectors = gson.getAdapter(Vector3i::class.java) + private val objects = gson.getAdapter(JsonObject::class.java) + + override fun write(out: JsonWriter, value: UniversePos) { + out.beginObject() + + out.name("location") + vectors.write(out, value.location) + + out.name("planet") + out.value(value.planetOrbit) + + out.name("satellite") + out.value(value.satelliteOrbit) + + out.endObject() + } + + override fun read(`in`: JsonReader): UniversePos { + if (`in`.peek() == JsonToken.BEGIN_OBJECT) { + val values = objects.read(`in`)!! + val location = values.get("location", vectors) + val planet = values.get("planet", 0) + val orbit = values.get("orbit", 0) + return UniversePos(location, planet, orbit) + } + + if (`in`.peek() == JsonToken.STRING) { + val read = `in`.nextString().trim() + + if (read == "" || read.lowercase() == "") + return ZERO + else { + try { + val split = read.split(splitter) + val x = split[0].toInt() + val y = split[1].toInt() + val z = split[2].toInt() + + val planet = if (split.size > 3) split[3].toInt() else 0 + val orbit = if (split.size > 4) split[4].toInt() else 0 + + if (planet <= 0) // TODO: ??? Determine, if this is a bug in original code + throw IndexOutOfBoundsException("Planetary orbit: $planet") + + if (orbit < 0) + throw IndexOutOfBoundsException("Satellite orbit: $orbit") + + return UniversePos(Vector3i(x, y, z), planet, orbit) + } catch (err: Throwable) { + throw JsonSyntaxException("Error parsing UniversePos from string", err) + } + } + } + + throw JsonSyntaxException("Invalid data type for UniversePos: ${`in`.peek()}") + } + } + + companion object { val CODEC = nativeCodec(::UniversePos, UniversePos::write) val LEGACY_CODEC = legacyCodec(::UniversePos, UniversePos::write) private val splitter = Regex("[ _:]") val ZERO = UniversePos() - - override fun create(gson: Gson, type: TypeToken): TypeAdapter? { - if (type.rawType == UniversePos::class.java) { - val vectors = gson.getAdapter(Vector3i::class.java) - val objects = gson.getAdapter(JsonObject::class.java) - - return object : TypeAdapter() { - override fun write(out: JsonWriter, value: UniversePos?) { - if (value == null) - out.nullValue() - else { - out.beginObject() - - out.name("location") - vectors.write(out, value.location) - - out.name("planet") - out.value(value.planetOrbit) - - out.name("satellite") - out.value(value.satelliteOrbit) - - out.endObject() - } - } - - override fun read(`in`: JsonReader): UniversePos? { - if (`in`.consumeNull()) - return null - - if (`in`.peek() == JsonToken.BEGIN_OBJECT) { - val values = objects.read(`in`)!! - val location = values.get("location", vectors) - val planet = values.get("planet", 0) - val orbit = values.get("orbit", 0) - return UniversePos(location, planet, orbit) - } - - if (`in`.peek() == JsonToken.STRING) { - val read = `in`.nextString().trim() - - if (read == "" || read.lowercase() == "") - return ZERO - else { - try { - val split = read.split(splitter) - val x = split[0].toInt() - val y = split[1].toInt() - val z = split[2].toInt() - - val planet = if (split.size > 3) split[3].toInt() else 0 - val orbit = if (split.size > 4) split[4].toInt() else 0 - - if (planet <= 0) // TODO: ??? Determine, if this is a bug in original code - throw IndexOutOfBoundsException("Planetary orbit: $planet") - - if (orbit < 0) - throw IndexOutOfBoundsException("Satellite orbit: $orbit") - - return UniversePos(Vector3i(x, y, z), planet, orbit) - } catch (err: Throwable) { - throw JsonSyntaxException("Error parsing UniversePos from string", err) - } - } - } - - throw JsonSyntaxException("Invalid data type for UniversePos: ${`in`.peek()}") - } - } as TypeAdapter - } - - return null - } } }