Minor cleanup, VersionedAdapter

This commit is contained in:
DBotThePony 2024-04-02 21:53:18 +07:00
parent 24fdf7ab70
commit 66a3f0304a
Signed by: DBot
GPG Key ID: DCC23B5715498507
24 changed files with 345 additions and 187 deletions

View File

@ -162,27 +162,67 @@ object Starbound : ISBFileLocator {
// immeasurably lazy and fragile solution, too bad! // 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!), // 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, // 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. // and there are cases of where discStore() calls toJson() on children data, despite it having its own discStore() too.
var IS_WRITING_LEGACY_JSON: Boolean by ThreadLocal.withInitial { false } var IS_LEGACY_JSON: Boolean by ThreadLocal.withInitial { false }
private set private set
var IS_WRITING_STORE_JSON: Boolean by ThreadLocal.withInitial { false } var IS_STORE_JSON: Boolean by ThreadLocal.withInitial { false }
private set private set
fun writeLegacyJson(data: Any): JsonElement { fun legacyJson(data: Any): JsonElement {
try { try {
IS_WRITING_LEGACY_JSON = true IS_LEGACY_JSON = true
return gson.toJsonTree(data) return gson.toJsonTree(data)
} finally { } finally {
IS_WRITING_LEGACY_JSON = false IS_LEGACY_JSON = false
} }
} }
fun <T> writeLegacyJson(block: () -> T): T { fun storeJson(data: Any): JsonElement {
try { 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 <T> legacyJson(block: () -> T): T {
try {
IS_LEGACY_JSON = true
return block.invoke() return block.invoke()
} finally { } finally {
IS_WRITING_LEGACY_JSON = false IS_LEGACY_JSON = false
}
}
fun <T> storeJson(block: () -> T): T {
try {
IS_STORE_JSON = true
return block.invoke()
} finally {
IS_STORE_JSON = false
}
}
fun <T> 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(RGBAColorTypeAdapter)
registerTypeAdapter(Drawable::Adapter)
registerTypeAdapter(ObjectOrientation::Adapter)
registerTypeAdapter(ObjectDefinition::Adapter)
registerTypeAdapter(StatModifier::Adapter)
registerTypeAdapterFactory(NativeLegacy.Companion) registerTypeAdapterFactory(NativeLegacy.Companion)
// математические классы // математические классы
@ -261,8 +296,6 @@ object Starbound : ISBFileLocator {
registerTypeAdapter(Vector4iTypeAdapter.nullSafe()) registerTypeAdapter(Vector4iTypeAdapter.nullSafe())
registerTypeAdapter(Vector4dTypeAdapter.nullSafe()) registerTypeAdapter(Vector4dTypeAdapter.nullSafe())
registerTypeAdapter(Vector4fTypeAdapter.nullSafe()) registerTypeAdapter(Vector4fTypeAdapter.nullSafe())
registerTypeAdapterFactory(Line2d.Companion)
registerTypeAdapterFactory(UniversePos.Companion)
registerTypeAdapterFactory(AbstractPerlinNoise.Companion) registerTypeAdapterFactory(AbstractPerlinNoise.Companion)
registerTypeAdapterFactory(WeightedList.Companion) registerTypeAdapterFactory(WeightedList.Companion)
@ -291,7 +324,6 @@ object Starbound : ISBFileLocator {
registerTypeAdapter(ItemStack.Adapter(this@Starbound)) registerTypeAdapter(ItemStack.Adapter(this@Starbound))
registerTypeAdapter(ItemDescriptor::Adapter)
registerTypeAdapterFactory(TreasurePoolDefinition.Companion) registerTypeAdapterFactory(TreasurePoolDefinition.Companion)
registerTypeAdapterFactory(UniverseChunk.Companion) registerTypeAdapterFactory(UniverseChunk.Companion)

View File

@ -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<String>()
fun currentVersion(name: String): Int {
return versions.getInt(name)
}
fun make(name: String, contents: JsonElement): VersionedJson {
return VersionedJson(name, currentVersion(name), contents)
}
fun <T> load(name: String, reader: JsonReader, adapter: TypeAdapter<T>): 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) }
}

View File

@ -31,6 +31,7 @@ import ru.dbotthepony.kstarbound.json.builder.FactoryAdapter
import ru.dbotthepony.kstarbound.json.builder.IStringSerializable import ru.dbotthepony.kstarbound.json.builder.IStringSerializable
import ru.dbotthepony.kstarbound.json.builder.JsonFactory import ru.dbotthepony.kstarbound.json.builder.JsonFactory
import ru.dbotthepony.kstarbound.json.getAdapter 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.legacyCodec
import ru.dbotthepony.kstarbound.network.syncher.nativeCodec import ru.dbotthepony.kstarbound.network.syncher.nativeCodec
import ru.dbotthepony.kstarbound.world.physics.Poly import ru.dbotthepony.kstarbound.world.physics.Poly
@ -111,7 +112,7 @@ data class TouchDamage(
@JsonAdapter(DamageSource.Adapter::class) @JsonAdapter(DamageSource.Adapter::class)
data class DamageSource( data class DamageSource(
val damageType: DamageType, val damageType: DamageType,
val damageArea: Either<Poly, Pair<Vector2d, Vector2d>>, val damageArea: Either<Poly, Line2d>,
val damage: Double, val damage: Double,
val trackSourceEntity: Boolean, val trackSourceEntity: Boolean,
val sourceEntityId: Int = 0, val sourceEntityId: Int = 0,
@ -125,7 +126,7 @@ data class DamageSource(
) { ) {
constructor(stream: DataInputStream, isLegacy: Boolean) : this( constructor(stream: DataInputStream, isLegacy: Boolean) : this(
DamageType.entries[stream.readUnsignedByte()], 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.readDouble(isLegacy),
stream.readBoolean(), stream.readBoolean(),
stream.readInt(), stream.readInt(),
@ -140,7 +141,7 @@ data class DamageSource(
data class JsonData( data class JsonData(
val poly: Poly? = null, val poly: Poly? = null,
val line: Pair<Vector2d, Vector2d>? = null, val line: Line2d? = null,
val damage: Double, val damage: Double,
val damageType: DamageType = DamageType.DAMAGE, val damageType: DamageType = DamageType.DAMAGE,
val trackSourceEntity: Boolean = true, val trackSourceEntity: Boolean = true,
@ -158,7 +159,7 @@ data class DamageSource(
fun write(stream: DataOutputStream, isLegacy: Boolean) { fun write(stream: DataOutputStream, isLegacy: Boolean) {
stream.writeByte(damageType.ordinal) 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.writeDouble(damage, isLegacy)
stream.writeBoolean(trackSourceEntity) stream.writeBoolean(trackSourceEntity)
stream.writeInt(sourceEntityId) stream.writeInt(sourceEntityId)

View File

@ -4,6 +4,7 @@ import com.google.common.collect.ImmutableList
import com.google.gson.Gson import com.google.gson.Gson
import com.google.gson.JsonObject import com.google.gson.JsonObject
import com.google.gson.TypeAdapter import com.google.gson.TypeAdapter
import com.google.gson.annotations.JsonAdapter
import com.google.gson.reflect.TypeToken import com.google.gson.reflect.TypeToken
import com.google.gson.stream.JsonReader import com.google.gson.stream.JsonReader
import com.google.gson.stream.JsonWriter 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.kommons.gson.contains
import ru.dbotthepony.kstarbound.math.Line2d import ru.dbotthepony.kstarbound.math.Line2d
@JsonAdapter(Drawable.Adapter::class)
sealed class Drawable(val position: Vector2f, val color: RGBAColor, val fullbright: Boolean) { sealed class Drawable(val position: Vector2f, val color: RGBAColor, val fullbright: Boolean) {
@JsonFactory @JsonFactory
data class Transformations(val centered: Boolean = false, val rotation: Float = 0f, val mirrored: Boolean = false, val scale: Either<Float, Vector2f> = Either.left(1f)) data class Transformations(val centered: Boolean = false, val rotation: Float = 0f, val mirrored: Boolean = false, val scale: Either<Float, Vector2f> = Either.left(1f))

View File

@ -4,6 +4,7 @@ import com.google.gson.Gson
import com.google.gson.JsonObject import com.google.gson.JsonObject
import com.google.gson.JsonSyntaxException import com.google.gson.JsonSyntaxException
import com.google.gson.TypeAdapter import com.google.gson.TypeAdapter
import com.google.gson.annotations.JsonAdapter
import com.google.gson.stream.JsonReader import com.google.gson.stream.JsonReader
import com.google.gson.stream.JsonWriter import com.google.gson.stream.JsonWriter
import ru.dbotthepony.kommons.gson.consumeNull import ru.dbotthepony.kommons.gson.consumeNull
@ -17,6 +18,7 @@ import ru.dbotthepony.kstarbound.network.syncher.nativeCodec
import java.io.DataInputStream import java.io.DataInputStream
import java.io.DataOutputStream import java.io.DataOutputStream
@JsonAdapter(StatModifier.Adapter::class)
data class StatModifier(val stat: String, val value: Double, val type: StatModifierType) { data class StatModifier(val stat: String, val value: Double, val type: StatModifierType) {
class Adapter(gson: Gson) : TypeAdapter<StatModifier>() { class Adapter(gson: Gson) : TypeAdapter<StatModifier>() {
private val objects = gson.getAdapter(JsonObject::class.java) private val objects = gson.getAdapter(JsonObject::class.java)

View File

@ -53,7 +53,7 @@ class Image private constructor(
val width: Int, val width: Int,
val height: Int, val height: Int,
val amountOfChannels: Int, val amountOfChannels: Int,
sprites: List<DataSprite>? spritesData: Pair<List<DataSprite>, IStarboundFile>?
) { ) {
init { init {
check(width >= 0) { "Invalid width $width" } check(width >= 0) { "Invalid width $width" }
@ -61,22 +61,45 @@ class Image private constructor(
check(amountOfChannels in 1 .. 4) { "Unknown number of channels $amountOfChannels" } check(amountOfChannels in 1 .. 4) { "Unknown number of channels $amountOfChannels" }
} }
private val spritesInternal = Object2ObjectLinkedOpenHashMap<String, Sprite>() private val spritesInternal = LinkedHashMap<String, Sprite>()
private var dataRef: WeakReference<ByteBuffer>? = null private var dataRef: WeakReference<ByteBuffer>? = null
private val lock = ReentrantLock() private val lock = ReentrantLock()
//private val _texture = ThreadLocal<WeakReference<GLTexture2D>>() //private val _texture = ThreadLocal<WeakReference<GLTexture2D>>()
init { init {
if (sprites == null) { if (spritesData == null) {
this.spritesInternal["default"] = Sprite("default", 0, 0, width, height) this.spritesInternal["default"] = Sprite("default", 0, 0, width, height)
} else { } else {
val (sprites, origin) = spritesData
for (data in sprites) { for (data in sprites) {
this.spritesInternal[data.name] = Sprite( var sX = data.coordinates.x % width
data.name, var sY = data.coordinates.y % height
data.coordinates.x,
data.coordinates.y, if (sX !in 0 .. width) {
data.coordinates.z - data.coordinates.x, //LOGGER.warn("Sprite X offset ${data.name} is out of bounds: $sX, clamping to 0 .. $width. (image: $source; frames: $origin)")
data.coordinates.w - data.coordinates.y) 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) { for (y in 0 until PIXELS_IN_STARBOUND_UNITi) {
val ypixel = (yspace * PIXELS_IN_STARBOUND_UNITi + y - pixelOffset.y) val ypixel = (yspace * PIXELS_IN_STARBOUND_UNITi + y - pixelOffset.y)
if (ypixel !in 0 until width) if (ypixel !in 0 until height)
continue continue
for (x in 0 until PIXELS_IN_STARBOUND_UNITi) { for (x in 0 until PIXELS_IN_STARBOUND_UNITi) {
@ -300,12 +323,12 @@ class Image private constructor(
} }
companion object : TypeAdapter<Image>() { companion object : TypeAdapter<Image>() {
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 objects by lazy { Starbound.gson.getAdapter(JsonObject::class.java) }
private val vectors by lazy { Starbound.gson.getAdapter(Vector4i::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 vectors2 by lazy { Starbound.gson.getAdapter(Vector2i::class.java) }
private val configCache = ConcurrentHashMap<String, Optional<List<DataSprite>>>() private val configCache = ConcurrentHashMap<String, Optional<Pair<List<DataSprite>, IStarboundFile>>>()
private val imageCache = ConcurrentHashMap<String, Optional<Image>>() private val imageCache = ConcurrentHashMap<String, Optional<Image>>()
private val logger = LogManager.getLogger() private val logger = LogManager.getLogger()
@ -494,17 +517,17 @@ class Image private constructor(
return ImmutableList.copyOf(sprites.values) return ImmutableList.copyOf(sprites.values)
} }
private fun compute(it: String): Optional<List<DataSprite>> { private fun compute(it: String): Optional<Pair<List<DataSprite>, IStarboundFile>> {
val find = Starbound.locate("$it.frames") val find = Starbound.locate("$it.frames")
if (!find.exists) { if (!find.exists) {
return Optional.empty() return Optional.empty()
} else { } 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<DataSprite>? { private fun getConfig(path: String): Pair<List<DataSprite>, IStarboundFile>? {
var folder = path.substringBefore(':').substringBeforeLast('/') var folder = path.substringBefore(':').substringBeforeLast('/')
val name = path.substringBefore(':').substringAfterLast('/').substringBefore('.') val name = path.substringBefore(':').substringAfterLast('/').substringBefore('.')

View File

@ -17,7 +17,7 @@ data class InventoryIcon(
override val image: SpriteReference override val image: SpriteReference
) : IInventoryIcon { ) : IInventoryIcon {
companion object : TypeAdapter<InventoryIcon>() { companion object : TypeAdapter<InventoryIcon>() {
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) } private val images by lazy { Starbound.gson.getAdapter(SpriteReference::class.java) }
override fun write(out: JsonWriter, value: InventoryIcon?) { override fun write(out: JsonWriter, value: InventoryIcon?) {

View File

@ -8,6 +8,7 @@ import com.google.gson.JsonObject
import com.google.gson.JsonPrimitive import com.google.gson.JsonPrimitive
import com.google.gson.JsonSyntaxException import com.google.gson.JsonSyntaxException
import com.google.gson.TypeAdapter import com.google.gson.TypeAdapter
import com.google.gson.annotations.JsonAdapter
import com.google.gson.stream.JsonReader import com.google.gson.stream.JsonReader
import com.google.gson.stream.JsonWriter import com.google.gson.stream.JsonWriter
import org.classdump.luna.ByteString import org.classdump.luna.ByteString
@ -28,6 +29,8 @@ import ru.dbotthepony.kommons.io.writeBinaryString
import ru.dbotthepony.kommons.io.writeVarLong import ru.dbotthepony.kommons.io.writeVarLong
import ru.dbotthepony.kstarbound.Registries import ru.dbotthepony.kstarbound.Registries
import ru.dbotthepony.kstarbound.Registry 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.defs.item.api.IItemDefinition
import ru.dbotthepony.kstarbound.io.readInternedString import ru.dbotthepony.kstarbound.io.readInternedString
import ru.dbotthepony.kstarbound.item.ItemStack 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 * [parameters] is considered to be immutable and should not be modified
* directly (must be copied for mutable context) * directly (must be copied for mutable context)
*/ */
@JsonAdapter(ItemDescriptor.Adapter::class)
data class ItemDescriptor( data class ItemDescriptor(
val name: String, val name: String,
val count: Long, val count: Long,
@ -123,15 +127,21 @@ data class ItemDescriptor(
return ItemStack.create(this) return ItemStack.create(this)
} }
fun toJson(): JsonObject? { private fun toJsonStruct() = JsonObject().also {
if (isEmpty) { it.add("name", JsonPrimitive(name))
return null it.add("count", JsonPrimitive(count))
} it.add("parameters", parameters.deepCopy())
}
return JsonObject().also { fun toJson(): JsonObject? {
it.add("name", JsonPrimitive(name)) if (Starbound.IS_STORE_JSON) {
it.add("count", JsonPrimitive(count)) return VersionRegistry.make("Item", toJsonStruct()).toJson()
it.add("parameters", parameters.deepCopy()) } else {
if (isEmpty) {
return null
}
return toJsonStruct()
} }
} }
@ -156,19 +166,14 @@ data class ItemDescriptor(
class Adapter(gson: Gson) : TypeAdapter<ItemDescriptor>() { class Adapter(gson: Gson) : TypeAdapter<ItemDescriptor>() {
private val elements = gson.getAdapter(JsonElement::class.java) private val elements = gson.getAdapter(JsonElement::class.java)
override fun write(out: JsonWriter, value: ItemDescriptor?) { override fun write(out: JsonWriter, value: ItemDescriptor) {
if (value == null) if (value.isEmpty)
out.nullValue()
else if (value.isEmpty)
out.nullValue() out.nullValue()
else else
out.value(value.toJson()) out.value(value.toJson())
} }
override fun read(`in`: JsonReader): ItemDescriptor { override fun read(`in`: JsonReader): ItemDescriptor {
if (`in`.consumeNull())
return EMPTY
return ItemDescriptor(elements.read(`in`)) return ItemDescriptor(elements.read(`in`))
} }
} }

View File

@ -45,7 +45,7 @@ interface IArmorItemDefinition : ILeveledItemDefinition, IScriptableItemDefiniti
override fun <T : Any?> create(gson: Gson, type: TypeToken<T>): TypeAdapter<T>? { override fun <T : Any?> create(gson: Gson, type: TypeToken<T>): TypeAdapter<T>? {
if (type.rawType == Frames::class.java) { if (type.rawType == Frames::class.java) {
return object : TypeAdapter<Frames>() { return object : TypeAdapter<Frames>() {
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) private val frames = gson.getAdapter(SpriteReference::class.java)

View File

@ -9,6 +9,7 @@ import com.google.gson.JsonObject
import com.google.gson.JsonPrimitive import com.google.gson.JsonPrimitive
import com.google.gson.JsonSyntaxException import com.google.gson.JsonSyntaxException
import com.google.gson.TypeAdapter import com.google.gson.TypeAdapter
import com.google.gson.annotations.JsonAdapter
import com.google.gson.stream.JsonReader import com.google.gson.stream.JsonReader
import com.google.gson.stream.JsonWriter import com.google.gson.stream.JsonWriter
import ru.dbotthepony.kommons.util.Either 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.animation.AnimationDefinition
import ru.dbotthepony.kstarbound.defs.item.ItemDescriptor import ru.dbotthepony.kstarbound.defs.item.ItemDescriptor
@JsonAdapter(ObjectDefinition.Adapter::class)
data class ObjectDefinition( data class ObjectDefinition(
val objectName: String, val objectName: String,
val objectType: ObjectType = ObjectType.OBJECT, val objectType: ObjectType = ObjectType.OBJECT,
@ -79,10 +81,6 @@ data class ObjectDefinition(
val flickerPeriod: PeriodicFunction? = null, val flickerPeriod: PeriodicFunction? = null,
val orientations: ImmutableList<ObjectOrientation>, val orientations: ImmutableList<ObjectOrientation>,
) { ) {
companion object {
}
class Adapter(gson: Gson) : TypeAdapter<ObjectDefinition>() { class Adapter(gson: Gson) : TypeAdapter<ObjectDefinition>() {
@JsonFactory(logMisses = false) @JsonFactory(logMisses = false)
data class PlainData( data class PlainData(

View File

@ -7,6 +7,7 @@ import com.google.gson.JsonArray
import com.google.gson.JsonObject import com.google.gson.JsonObject
import com.google.gson.JsonSyntaxException import com.google.gson.JsonSyntaxException
import com.google.gson.TypeAdapter import com.google.gson.TypeAdapter
import com.google.gson.annotations.JsonAdapter
import com.google.gson.reflect.TypeToken import com.google.gson.reflect.TypeToken
import com.google.gson.stream.JsonReader import com.google.gson.stream.JsonReader
import com.google.gson.stream.JsonWriter import com.google.gson.stream.JsonWriter
@ -33,6 +34,7 @@ import ru.dbotthepony.kstarbound.defs.tile.TileDefinition
import ru.dbotthepony.kstarbound.world.Side import ru.dbotthepony.kstarbound.world.Side
import kotlin.math.PI import kotlin.math.PI
@JsonAdapter(ObjectOrientation.Adapter::class)
data class ObjectOrientation( data class ObjectOrientation(
val json: JsonObject, val json: JsonObject,
val flipImages: Boolean = false, val flipImages: Boolean = false,
@ -173,10 +175,23 @@ data class ObjectOrientation(
} }
} }
val minX = occupySpaces.minOf { it.x } val minX: Int
val minY = occupySpaces.minOf { it.y } val minY: Int
val maxX = occupySpaces.maxOf { it.x } val maxX: Int
val maxY = occupySpaces.maxOf { it.y } 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 boundingBox = AABBi(Vector2i(minX, minY), Vector2i(maxX, maxY))
val metaBoundBox = obj["metaBoundBox"]?.let { aabbs.fromJsonTree(it) } val metaBoundBox = obj["metaBoundBox"]?.let { aabbs.fromJsonTree(it) }

View File

@ -1,17 +1,28 @@
package ru.dbotthepony.kstarbound.defs.quest package ru.dbotthepony.kstarbound.defs.quest
import com.google.common.collect.ImmutableList 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.readCollection
import ru.dbotthepony.kommons.io.writeBinaryString import ru.dbotthepony.kommons.io.writeBinaryString
import ru.dbotthepony.kommons.io.writeCollection 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.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.builder.JsonFactory
import ru.dbotthepony.kstarbound.json.getAdapter
import ru.dbotthepony.kstarbound.network.syncher.legacyCodec import ru.dbotthepony.kstarbound.network.syncher.legacyCodec
import ru.dbotthepony.kstarbound.network.syncher.nativeCodec import ru.dbotthepony.kstarbound.network.syncher.nativeCodec
import java.io.DataInputStream import java.io.DataInputStream
import java.io.DataOutputStream import java.io.DataOutputStream
@JsonFactory @JsonAdapter(QuestArcDescriptor.Adapter::class)
data class QuestArcDescriptor( data class QuestArcDescriptor(
val quests: ImmutableList<QuestDescriptor> = ImmutableList.of(), val quests: ImmutableList<QuestDescriptor> = ImmutableList.of(),
val stagehandUniqueId: String? = null, val stagehandUniqueId: String? = null,
@ -27,6 +38,8 @@ data class QuestArcDescriptor(
if (stagehandUniqueId != null) stream.writeBinaryString(stagehandUniqueId) if (stagehandUniqueId != null) stream.writeBinaryString(stagehandUniqueId)
} }
class Adapter(gson: Gson) : VersionedAdapter<QuestArcDescriptor>("QuestArcDescriptor", FactoryAdapter.createFor(QuestArcDescriptor::class, gson))
companion object { companion object {
val CODEC = nativeCodec(::QuestArcDescriptor, QuestArcDescriptor::write) val CODEC = nativeCodec(::QuestArcDescriptor, QuestArcDescriptor::write)
val LEGACY_CODEC = legacyCodec(::QuestArcDescriptor, QuestArcDescriptor::write) val LEGACY_CODEC = legacyCodec(::QuestArcDescriptor, QuestArcDescriptor::write)

View File

@ -1,18 +1,23 @@
package ru.dbotthepony.kstarbound.defs.quest package ru.dbotthepony.kstarbound.defs.quest
import com.google.common.collect.ImmutableMap import com.google.common.collect.ImmutableMap
import com.google.gson.Gson
import com.google.gson.JsonObject 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.readMap
import ru.dbotthepony.kommons.io.writeBinaryString import ru.dbotthepony.kommons.io.writeBinaryString
import ru.dbotthepony.kommons.io.writeMap import ru.dbotthepony.kommons.io.writeMap
import ru.dbotthepony.kstarbound.io.readInternedString 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.json.builder.JsonFactory
import ru.dbotthepony.kstarbound.network.syncher.legacyCodec import ru.dbotthepony.kstarbound.network.syncher.legacyCodec
import ru.dbotthepony.kstarbound.network.syncher.nativeCodec import ru.dbotthepony.kstarbound.network.syncher.nativeCodec
import java.io.DataInputStream import java.io.DataInputStream
import java.io.DataOutputStream import java.io.DataOutputStream
@JsonFactory @JsonAdapter(QuestDescriptor.Adapter::class)
data class QuestDescriptor( data class QuestDescriptor(
val questId: String, val questId: String,
val templateId: String = questId, val templateId: String = questId,
@ -33,6 +38,8 @@ data class QuestDescriptor(
stream.writeLong(seed) stream.writeLong(seed)
} }
class Adapter(gson: Gson) : VersionedAdapter<QuestDescriptor>("QuestDescriptor", FactoryAdapter.createFor(QuestDescriptor::class, gson))
companion object { companion object {
val CODEC = nativeCodec(::QuestDescriptor, QuestDescriptor::write) val CODEC = nativeCodec(::QuestDescriptor, QuestDescriptor::write)
val LEGACY_CODEC = legacyCodec(::QuestDescriptor, QuestDescriptor::write) val LEGACY_CODEC = legacyCodec(::QuestDescriptor, QuestDescriptor::write)

View File

@ -140,7 +140,7 @@ abstract class VisitableWorldParameters {
this.weatherPool = read.weatherPool 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( val store = StoreData(
threatLevel, threatLevel,
typeName, typeName,
@ -164,7 +164,7 @@ abstract class VisitableWorldParameters {
data["type"] = type.jsonName 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() val data = JsonObject()
toJson(data, isLegacy) toJson(data, isLegacy)
return data return data

View File

@ -211,7 +211,7 @@ class WorldLayout {
return Starbound.gson.toJsonTree(SerializedForm( return Starbound.gson.toJsonTree(SerializedForm(
worldSize, regionBlending, blockNoise, blendNoise, worldSize, regionBlending, blockNoise, blendNoise,
playerStartSearchRegions, biomes.list, terrainSelectors.list, 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 )) as JsonObject
} }

View File

@ -1,18 +1,13 @@
package ru.dbotthepony.kstarbound.defs.world package ru.dbotthepony.kstarbound.defs.world
import com.google.gson.JsonElement
import com.google.gson.JsonNull
import com.google.gson.JsonObject import com.google.gson.JsonObject
import ru.dbotthepony.kommons.gson.set
import ru.dbotthepony.kommons.util.Either import ru.dbotthepony.kommons.util.Either
import ru.dbotthepony.kommons.vector.Vector2i import ru.dbotthepony.kommons.vector.Vector2i
import ru.dbotthepony.kstarbound.Starbound import ru.dbotthepony.kstarbound.Starbound
import ru.dbotthepony.kstarbound.fromJson
import ru.dbotthepony.kstarbound.json.builder.JsonFactory import ru.dbotthepony.kstarbound.json.builder.JsonFactory
import ru.dbotthepony.kstarbound.world.Universe import ru.dbotthepony.kstarbound.world.Universe
import ru.dbotthepony.kstarbound.world.UniversePos import ru.dbotthepony.kstarbound.world.UniversePos
import ru.dbotthepony.kstarbound.world.WorldGeometry import ru.dbotthepony.kstarbound.world.WorldGeometry
import kotlin.properties.Delegates
class WorldTemplate(val geometry: WorldGeometry) { class WorldTemplate(val geometry: WorldGeometry) {
var seed: Long = 0L var seed: Long = 0L
@ -58,7 +53,7 @@ class WorldTemplate(val geometry: WorldGeometry) {
fun toJson(): JsonObject { fun toJson(): JsonObject {
val data = Starbound.gson.toJsonTree(SerializedForm( val data = Starbound.gson.toJsonTree(SerializedForm(
celestialParameters, worldParameters, skyParameters, seed, 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 worldLayout
)) as JsonObject )) as JsonObject

View File

@ -106,7 +106,7 @@ abstract class NativeLegacy<NATIVE, LEGACY> {
override fun write(out: JsonWriter, value: NativeLegacy<L, R>?) { override fun write(out: JsonWriter, value: NativeLegacy<L, R>?) {
if (value == null) if (value == null)
out.nullValue() 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) right.write(out, value.legacy)
else else
left.write(out, value.native) left.write(out, value.native)

View File

@ -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<T>(val name: String, val parent: TypeAdapter<T>) : TypeAdapter<T>() {
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`)
}
}
}

View File

@ -1,12 +1,29 @@
package ru.dbotthepony.kstarbound.json package ru.dbotthepony.kstarbound.json
import com.google.gson.JsonElement 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.kommons.io.readBinaryString
import ru.dbotthepony.kstarbound.Starbound
import ru.dbotthepony.kstarbound.json.builder.JsonFactory
import java.io.DataInputStream import java.io.DataInputStream
@JsonFactory
data class VersionedJson(val identifier: String, val version: Int?, val content: JsonElement) { data class VersionedJson(val identifier: String, val version: Int?, val content: JsonElement) {
constructor(data: DataInputStream) : this( constructor(data: DataInputStream) : this(
data.readBinaryString(), data.readBinaryString(),
data.read().let { if (it > 0) data.readInt() else null }, data.read().let { if (it > 0) data.readInt() else null },
data.readJsonElement()) 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<VersionedJson>() }
}
} }

View File

@ -484,7 +484,7 @@ class FactoryAdapter<T : Any> private constructor(
companion object { companion object {
private val LOGGER = LogManager.getLogger() private val LOGGER = LogManager.getLogger()
fun <T : Any> createFor(kclass: KClass<T>, config: JsonFactory = JsonFactory(), gson: Gson, stringInterner: Interner<String> = Starbound.STRINGS): TypeAdapter<T> { fun <T : Any> createFor(kclass: KClass<T>, gson: Gson, config: JsonFactory = JsonFactory(), stringInterner: Interner<String> = Starbound.STRINGS): TypeAdapter<T> {
val builder = Builder(kclass) val builder = Builder(kclass)
val properties = kclass.declaredMembers.filterIsInstance<KProperty1<T, *>>() val properties = kclass.declaredMembers.filterIsInstance<KProperty1<T, *>>()
@ -537,7 +537,7 @@ class FactoryAdapter<T : Any> private constructor(
val bconfig = first[0] as JsonFactory val bconfig = first[0] as JsonFactory
val kclass = raw.kotlin as KClass<T> val kclass = raw.kotlin as KClass<T>
return createFor(kclass, bconfig, gson, stringInterner) return createFor(kclass, gson, bconfig, stringInterner)
} }
return null return null

View File

@ -3,12 +3,19 @@ package ru.dbotthepony.kstarbound.math
import com.google.gson.Gson import com.google.gson.Gson
import com.google.gson.TypeAdapter import com.google.gson.TypeAdapter
import com.google.gson.TypeAdapterFactory import com.google.gson.TypeAdapterFactory
import com.google.gson.annotations.JsonAdapter
import com.google.gson.reflect.TypeToken import com.google.gson.reflect.TypeToken
import com.google.gson.stream.JsonReader import com.google.gson.stream.JsonReader
import com.google.gson.stream.JsonWriter import com.google.gson.stream.JsonWriter
import ru.dbotthepony.kommons.gson.consumeNull import ru.dbotthepony.kommons.gson.consumeNull
import ru.dbotthepony.kommons.util.KOptional import ru.dbotthepony.kommons.util.KOptional
import ru.dbotthepony.kommons.vector.Vector2d 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 import kotlin.math.absoluteValue
private operator fun Vector2d.compareTo(other: Vector2d): Int { private operator fun Vector2d.compareTo(other: Vector2d): Int {
@ -17,7 +24,10 @@ private operator fun Vector2d.compareTo(other: Vector2d): Int {
return cmp return cmp
} }
@JsonAdapter(Line2d.Adapter::class)
data class Line2d(val a: Vector2d, val b: Vector2d) { 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<Vector2d>, val t: KOptional<Double>, val coincides: Boolean, val glances: Boolean) { data class Intersection(val intersects: Boolean, val point: KOptional<Vector2d>, val t: KOptional<Double>, val coincides: Boolean, val glances: Boolean) {
companion object { companion object {
val EMPTY = Intersection(false, KOptional(), KOptional(), false, false) 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) return Line2d(b, a)
} }
fun write(stream: DataOutputStream, isLegacy: Boolean) {
stream.writeStruct2d(a, isLegacy)
stream.writeStruct2d(b, isLegacy)
}
// original source of this intersection algorithm: // original source of this intersection algorithm:
// https://stackoverflow.com/questions/563198/how-do-you-detect-where-two-line-segments-intersect // 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 // 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 return (other - a + difference() * proj).length
} }
companion object : TypeAdapterFactory { class Adapter(gson: Gson) : TypeAdapter<Line2d>() {
const val NEAR_ZERO = Double.MIN_VALUE * 2.0 private val pair = gson.getAdapter<Pair<Vector2d, Vector2d>>()
const val NEAR_ONE = 1.0 - NEAR_ZERO
override fun <T : Any> create(gson: Gson, type: TypeToken<T>): TypeAdapter<T>? { override fun write(out: JsonWriter, value: Line2d) {
if (type.rawType == Line2d::class.java) { pair.write(out, value.a to value.b)
val pair = gson.getAdapter(TypeToken.getParameterized(Vector2d::class.java, Vector2d::class.java)) as TypeAdapter<Pair<Vector2d, Vector2d>> }
return object : TypeAdapter<Line2d>() { override fun read(`in`: JsonReader): Line2d {
override fun write(out: JsonWriter, value: Line2d?) { val (a, b) = pair.read(`in`)
if (value == null) return Line2d(a, b)
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<T>
}
return null
} }
} }
companion object {
const val NEAR_ZERO = Double.MIN_VALUE * 2.0
const val NEAR_ONE = 1.0 - NEAR_ZERO
}
} }

View File

@ -145,17 +145,18 @@ class ServerConnection(val server: StarboundServer, type: ConnectionType) : Conn
if (world == null) { if (world == null) {
send(PlayerWarpResultPacket(false, request, false)) send(PlayerWarpResultPacket(false, request, false))
} else { continue
try { }
world.acceptClient(this, request).await()
} catch (err: Throwable) {
send(PlayerWarpResultPacket(false, request, false))
if (world == shipWorld) { try {
disconnect("ShipWorld refused to accept its owner: $err") world.acceptClient(this, request).await()
} else { } catch (err: Throwable) {
enqueueWarp(returnWarp ?: WarpAlias.OwnShip) send(PlayerWarpResultPacket(false, request, false))
}
if (world == shipWorld) {
disconnect("ShipWorld refused to accept its owner: $err")
} else {
enqueueWarp(returnWarp ?: WarpAlias.OwnShip)
} }
} }
} }

View File

@ -33,7 +33,6 @@ import ru.dbotthepony.kstarbound.world.TileHealth
import ru.dbotthepony.kstarbound.world.api.ImmutableCell import ru.dbotthepony.kstarbound.world.api.ImmutableCell
import ru.dbotthepony.kstarbound.world.entities.AbstractEntity import ru.dbotthepony.kstarbound.world.entities.AbstractEntity
import ru.dbotthepony.kstarbound.world.entities.player.PlayerEntity import ru.dbotthepony.kstarbound.world.entities.player.PlayerEntity
import ru.dbotthepony.kstarbound.world.entities.tile.WorldObject
import java.io.DataOutputStream import java.io.DataOutputStream
import java.util.HashMap import java.util.HashMap
import java.util.concurrent.ConcurrentLinkedQueue import java.util.concurrent.ConcurrentLinkedQueue
@ -91,7 +90,7 @@ class ServerWorldTracker(val world: ServerWorld, val client: ServerConnection, p
if (client.isLegacy) { if (client.isLegacy) {
client.send(WorldStartPacket( client.send(WorldStartPacket(
templateData = Starbound.writeLegacyJson { world.template.toJson() }, templateData = Starbound.legacyJson { world.template.toJson() },
skyData = skyData.toByteArray(), skyData = skyData.toByteArray(),
weatherData = ByteArray(0), weatherData = ByteArray(0),
playerStart = playerStart, playerStart = playerStart,
@ -105,7 +104,7 @@ class ServerWorldTracker(val world: ServerWorld, val client: ServerConnection, p
localInterpolationMode = false, localInterpolationMode = false,
)) ))
Starbound.writeLegacyJson { Starbound.legacyJson {
client.sendAndFlush(CentralStructureUpdatePacket(Starbound.gson.toJsonTree(world.centralStructure))) client.sendAndFlush(CentralStructureUpdatePacket(Starbound.gson.toJsonTree(world.centralStructure)))
} }
} }

View File

@ -5,6 +5,7 @@ import com.google.gson.JsonObject
import com.google.gson.JsonSyntaxException import com.google.gson.JsonSyntaxException
import com.google.gson.TypeAdapter import com.google.gson.TypeAdapter
import com.google.gson.TypeAdapterFactory import com.google.gson.TypeAdapterFactory
import com.google.gson.annotations.JsonAdapter
import com.google.gson.reflect.TypeToken import com.google.gson.reflect.TypeToken
import com.google.gson.stream.JsonReader import com.google.gson.stream.JsonReader
import com.google.gson.stream.JsonToken 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 * No validity checking is done here, any coordinate to any body whether it
* exists in a specific universe or not can be expressed. * 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) { 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()) 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<UniversePos>() {
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 CODEC = nativeCodec(::UniversePos, UniversePos::write)
val LEGACY_CODEC = legacyCodec(::UniversePos, UniversePos::write) val LEGACY_CODEC = legacyCodec(::UniversePos, UniversePos::write)
private val splitter = Regex("[ _:]") private val splitter = Regex("[ _:]")
val ZERO = UniversePos() val ZERO = UniversePos()
override fun <T : Any> create(gson: Gson, type: TypeToken<T>): TypeAdapter<T>? {
if (type.rawType == UniversePos::class.java) {
val vectors = gson.getAdapter(Vector3i::class.java)
val objects = gson.getAdapter(JsonObject::class.java)
return object : TypeAdapter<UniversePos>() {
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<T>
}
return null
}
} }
} }