package ru.dbotthepony.kstarbound.defs 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.readUUID import ru.dbotthepony.kommons.io.writeBinaryString import ru.dbotthepony.kommons.io.writeUUID import ru.dbotthepony.kstarbound.io.readInternedString import ru.dbotthepony.kstarbound.network.syncher.legacyCodec import ru.dbotthepony.kstarbound.network.syncher.nativeCodec import ru.dbotthepony.kstarbound.util.toStarboundString import ru.dbotthepony.kstarbound.util.uuidFromStarboundString import ru.dbotthepony.kstarbound.world.UniversePos import java.io.DataInputStream import java.io.DataOutputStream import java.util.UUID @JsonAdapter(WorldID.Adapter::class) sealed class WorldID { abstract fun write(stream: DataOutputStream, isLegacy: Boolean) val isLimbo: Boolean get() = this is Limbo object Limbo : WorldID() { override fun write(stream: DataOutputStream, isLegacy: Boolean) { stream.writeByte(0) } override fun toString(): String { return "Nowhere" } } data class Celestial(val pos: UniversePos) : WorldID() { override fun write(stream: DataOutputStream, isLegacy: Boolean) { stream.writeByte(1) pos.write(stream, isLegacy) } override fun toString(): String { return "CelestialWorld:$pos" } } data class ShipWorld(val uuid: UUID) : WorldID() { override fun write(stream: DataOutputStream, isLegacy: Boolean) { stream.writeByte(2) stream.writeUUID(uuid) } override fun toString(): String { return "ClientShipWorld:${uuid.toStarboundString()}" } } data class Instance(val name: String, val uuid: UUID? = null, val threatLevel: Double? = null) : WorldID() { override fun write(stream: DataOutputStream, isLegacy: Boolean) { stream.writeByte(3) stream.writeBinaryString(name) stream.writeBoolean(uuid != null) if (uuid != null) stream.writeUUID(uuid) stream.writeBoolean(threatLevel != null) if (threatLevel != null) { if (isLegacy) { stream.writeFloat(threatLevel.toFloat()) } else { stream.writeDouble(threatLevel) } } } override fun toString(): String { return "InstanceWorld:$name:${uuid?.toStarboundString() ?: "-"}:${threatLevel ?: "-"}" } } class Adapter(gson: Gson) : TypeAdapter() { override fun write(out: JsonWriter, value: WorldID) { out.value(value.toString()) } override fun read(`in`: JsonReader): WorldID { return parse(`in`.nextString()) } } companion object { val CODEC = nativeCodec(::read, WorldID::write) val LEGACY_CODEC = legacyCodec(::read, WorldID::write) fun parse(value: String): WorldID { if (value.isBlank()) return Limbo val parts = value.split(':') return when (val type = parts[0].lowercase()) { "nowhere" -> Limbo "instanceworld" -> { val rest = parts[1].split(':') if (rest.isEmpty() || rest.size > 3) { throw IllegalArgumentException("Malformed InstanceWorld string: $value") } val name = rest[0] var uuid: UUID? = null var threatLevel: Double? = null if (rest.size > 1) { uuid = if (rest[1] == "-") null else uuidFromStarboundString(rest[1]) if (rest.size > 2) { threatLevel = if (rest[2] == "-") null else rest[2].toDouble() if (threatLevel != null && threatLevel < 0.0) throw IllegalArgumentException("InstanceWorld threat level is negative: $value") } } Instance(name, uuid, threatLevel) } "celestialworld" -> Celestial(UniversePos.parse(parts[1])) "clientshipworld" -> ShipWorld(uuidFromStarboundString(parts[1])) else -> throw IllegalArgumentException("Invalid WorldID type: $type (input: $value)") } } fun read(stream: DataInputStream, isLegacy: Boolean): WorldID { return when (val type = stream.readUnsignedByte()) { 0 -> Limbo 1 -> Celestial(UniversePos(stream, isLegacy)) 2 -> ShipWorld(stream.readUUID()) 3 -> { val name = stream.readInternedString() val uuid = if (stream.readBoolean()) stream.readUUID() else null val level: Double? if (stream.readBoolean()) { if (isLegacy) { level = stream.readFloat().toDouble() } else { level = stream.readDouble() } } else { level = null } Instance(name, uuid, level) } else -> throw IllegalArgumentException("Unknown WorldID type $type!") } } } }