KStarbound/src/main/kotlin/ru/dbotthepony/kstarbound/world/UniversePos.kt

193 lines
5.8 KiB
Kotlin

package ru.dbotthepony.kstarbound.world
import com.google.gson.Gson
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.JsonToken
import com.google.gson.stream.JsonWriter
import ru.dbotthepony.kommons.gson.get
import ru.dbotthepony.kstarbound.math.vector.Vector3i
import ru.dbotthepony.kommons.io.readVarInt
import ru.dbotthepony.kstarbound.io.readVector3i
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<UniversePos> {
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<UniversePos>() {
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 satellite = values.get("satellite", 0)
return UniversePos(location, planet, satellite)
}
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)
}
}
}