package ru.dbotthepony.kstarbound.world import com.google.gson.Gson 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 import com.google.gson.stream.JsonWriter import ru.dbotthepony.kommons.gson.get import ru.dbotthepony.kommons.vector.Vector3i import ru.dbotthepony.kommons.gson.consumeNull import ru.dbotthepony.kommons.io.readVarInt import ru.dbotthepony.kommons.io.readVector3i import ru.dbotthepony.kommons.io.writeSignedVarInt import ru.dbotthepony.kommons.io.writeStruct3i import ru.dbotthepony.kommons.io.writeVarInt import ru.dbotthepony.kstarbound.Starbound import ru.dbotthepony.kstarbound.network.syncher.legacyCodec import ru.dbotthepony.kstarbound.network.syncher.nativeCodec import java.io.DataInputStream import java.io.DataOutputStream /** * Specifies coordinates to either a planetary system, a planetary body, or a * satellite around such a planetary body. The terms here are meant to be very * generic, a "planetary body" could be an asteroid field, or a ship, or * anything in orbit around the center of mass of a specific planetary system. * The terms are really simply meant as a hierarchy of orbits. * * 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) : Comparable { constructor(stream: DataInputStream, isLegacy: Boolean) : this(stream.readVector3i(), if (isLegacy) stream.readInt() else stream.readVarInt(), if (isLegacy) stream.readInt() else stream.readVarInt()) init { require(planetOrbit >= 0) { "Negative planetOrbit: $planetOrbit" } require(satelliteOrbit >= 0) { "Negative satelliteOrbit: $satelliteOrbit" } } override fun toString(): String { if (planetOrbit == 0 && satelliteOrbit == 0) return "${location.x}:${location.y}:${location.z}" else if (satelliteOrbit == 0) return "${location.x}:${location.y}:${location.z}:$planetOrbit" else return "${location.x}:${location.y}:${location.z}:$planetOrbit:$satelliteOrbit" } override fun compareTo(other: UniversePos): Int { var cmp = location.x.compareTo(other.location.x) if (cmp != 0) cmp = location.y.compareTo(other.location.y) if (cmp != 0) cmp = location.z.compareTo(other.location.z) if (cmp != 0) cmp = planetOrbit.compareTo(other.planetOrbit) if (cmp != 0) cmp = satelliteOrbit.compareTo(other.satelliteOrbit) return cmp } val isSystem: Boolean get() = planetOrbit == 0 val isPlanet: Boolean get() = planetOrbit != 0 && satelliteOrbit == 0 val isSatellite: Boolean get() = planetOrbit != 0 && satelliteOrbit != 0 val orbitNumber: Int get() = if (isSatellite) satelliteOrbit else if (isPlanet) planetOrbit else 0 fun system(): UniversePos { if (planetOrbit == 0 && satelliteOrbit == 0) return this return UniversePos(location) } fun planet(): UniversePos { if (satelliteOrbit == 0) return this return UniversePos(location, planetOrbit) } fun satellite(): UniversePos { return this } fun parent(): UniversePos { if (isSatellite) return planet() else if (isPlanet) return system() else return this } fun child(orbit: Int): UniversePos { if (isSatellite) throw IllegalStateException("Satellite can't have children!") else if (isPlanet) return UniversePos(location, planetOrbit, orbit) else return UniversePos(location, orbit) } fun write(stream: DataOutputStream, isLegacy: Boolean) { stream.writeStruct3i(location) if (isLegacy) { stream.writeInt(planetOrbit) stream.writeInt(satelliteOrbit) } else { stream.writeVarInt(planetOrbit) stream.writeVarInt(satelliteOrbit) } } class Adapter(gson: Gson) : TypeAdapter() { private val vectors = gson.getAdapter(Vector3i::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 = Starbound.ELEMENTS_ADAPTER.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 { return parse(read) } 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() fun parse(value: String): UniversePos { if (value.isBlank()) return ZERO val split = value.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("Non-positive planetary orbit: $planet (in $value)") if (orbit < 0) throw IndexOutOfBoundsException("Negative satellite orbit: $orbit (in $value)") return UniversePos(Vector3i(x, y, z), planet, orbit) } } }