300 lines
8.5 KiB
Kotlin
300 lines
8.5 KiB
Kotlin
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.StreamCodec
|
|
import ru.dbotthepony.kommons.io.readUUID
|
|
import ru.dbotthepony.kstarbound.io.readVector2d
|
|
import ru.dbotthepony.kstarbound.io.readVector2f
|
|
import ru.dbotthepony.kommons.io.writeBinaryString
|
|
import ru.dbotthepony.kommons.io.writeStruct2d
|
|
import ru.dbotthepony.kommons.io.writeStruct2f
|
|
import ru.dbotthepony.kommons.io.writeUUID
|
|
import ru.dbotthepony.kstarbound.math.vector.Vector2d
|
|
import ru.dbotthepony.kstarbound.io.readInternedString
|
|
import ru.dbotthepony.kstarbound.json.builder.IStringSerializable
|
|
import ru.dbotthepony.kstarbound.network.syncher.legacyCodec
|
|
import ru.dbotthepony.kstarbound.network.syncher.nativeCodec
|
|
import ru.dbotthepony.kstarbound.server.ServerConnection
|
|
import ru.dbotthepony.kstarbound.server.world.ServerWorld
|
|
import java.io.DataInputStream
|
|
import java.io.DataOutputStream
|
|
import java.util.UUID
|
|
import kotlin.math.roundToInt
|
|
|
|
// original game has MVariant here
|
|
// MVariant prepends InvalidValue to Variant<> template
|
|
// Variant<> itself works with LookupTypeIndex<MatchType, 0, FirstType, RestTypes...>
|
|
// 0 is responsible for holding current template comparison check
|
|
// typedef MVariant<WarpToWorld, WarpToPlayer, WarpAlias> WarpAction;
|
|
// -> Variant<InvalidType, WarpToWorld, WarpToPlayer, WarpAlias> WarpAction
|
|
// hence WarpToWorld has index 1, WarpToPlayer 2, WarpAlias 3
|
|
@JsonAdapter(SpawnTarget.Adapter::class)
|
|
sealed class SpawnTarget {
|
|
abstract fun write(stream: DataOutputStream, isLegacy: Boolean)
|
|
abstract fun resolve(world: ServerWorld): Vector2d?
|
|
|
|
object Whatever : SpawnTarget() {
|
|
override fun write(stream: DataOutputStream, isLegacy: Boolean) {
|
|
stream.writeByte(0)
|
|
}
|
|
|
|
override fun resolve(world: ServerWorld): Vector2d {
|
|
return world.playerSpawnPosition
|
|
}
|
|
|
|
override fun toString(): String {
|
|
return "Whatever"
|
|
}
|
|
}
|
|
|
|
data class Entity(val id: String) : SpawnTarget() {
|
|
override fun write(stream: DataOutputStream, isLegacy: Boolean) {
|
|
stream.writeByte(1)
|
|
stream.writeBinaryString(id)
|
|
}
|
|
|
|
override fun resolve(world: ServerWorld): Vector2d? {
|
|
return world.entities.values.firstOrNull { it.uniqueID.get().orNull() == id }?.position
|
|
}
|
|
|
|
override fun toString(): String {
|
|
return id
|
|
}
|
|
}
|
|
|
|
data class Position(val position: Vector2d) : SpawnTarget() {
|
|
override fun write(stream: DataOutputStream, isLegacy: Boolean) {
|
|
stream.writeByte(2)
|
|
|
|
if (isLegacy) {
|
|
stream.writeStruct2f(position.toFloatVector())
|
|
} else {
|
|
stream.writeStruct2d(position)
|
|
}
|
|
}
|
|
|
|
override fun toString(): String {
|
|
return "${position.x.roundToInt()}.${position.y.roundToInt()}"
|
|
}
|
|
|
|
override fun resolve(world: ServerWorld): Vector2d {
|
|
return position
|
|
}
|
|
}
|
|
|
|
data class X(val position: Double) : SpawnTarget() {
|
|
override fun write(stream: DataOutputStream, isLegacy: Boolean) {
|
|
stream.writeByte(3)
|
|
|
|
if (isLegacy) {
|
|
stream.writeFloat(position.toFloat())
|
|
} else {
|
|
stream.writeDouble(position)
|
|
}
|
|
}
|
|
|
|
override fun toString(): String {
|
|
return position.roundToInt().toString()
|
|
}
|
|
|
|
override fun resolve(world: ServerWorld): Vector2d {
|
|
TODO("Not yet implemented")
|
|
}
|
|
}
|
|
|
|
class Adapter : TypeAdapter<SpawnTarget>() {
|
|
override fun write(out: JsonWriter, value: SpawnTarget) {
|
|
out.value(value.toString())
|
|
}
|
|
|
|
override fun read(`in`: JsonReader): SpawnTarget {
|
|
return parse(`in`.nextString())
|
|
}
|
|
}
|
|
|
|
companion object {
|
|
private val position = Regex("\\d+.\\d+")
|
|
private val positionX = Regex("\\d+")
|
|
|
|
fun read(stream: DataInputStream, isLegacy: Boolean): SpawnTarget {
|
|
return when (val type = stream.readUnsignedByte()) {
|
|
0 -> Whatever
|
|
1 -> Entity(stream.readInternedString())
|
|
2 -> Position(if (isLegacy) stream.readVector2f().toDoubleVector() else stream.readVector2d())
|
|
3 -> X(if (isLegacy) stream.readFloat().toDouble() else stream.readDouble())
|
|
else -> throw IllegalArgumentException("Unknown SpawnTarget type $type!")
|
|
}
|
|
}
|
|
|
|
fun parse(value: String): SpawnTarget {
|
|
val matchPos = position.matchEntire(value)
|
|
|
|
if (matchPos != null) {
|
|
return Position(Vector2d(matchPos.groups[0]!!.value.toDouble(), matchPos.groups[0]!!.value.toDouble()))
|
|
}
|
|
|
|
val matchX = positionX.matchEntire(value)
|
|
|
|
if (matchX != null) {
|
|
return X(matchX.groups[0]!!.value.toDouble())
|
|
}
|
|
|
|
return Entity(value)
|
|
}
|
|
}
|
|
}
|
|
|
|
@JsonAdapter(WarpAction.Adapter::class)
|
|
sealed class WarpAction {
|
|
abstract fun write(stream: DataOutputStream, isLegacy: Boolean)
|
|
abstract fun resolve(connection: ServerConnection): WorldID
|
|
|
|
data class World(val worldID: WorldID, val target: SpawnTarget = SpawnTarget.Whatever) : WarpAction() {
|
|
override fun write(stream: DataOutputStream, isLegacy: Boolean) {
|
|
stream.writeByte(1)
|
|
worldID.write(stream, isLegacy)
|
|
target.write(stream, isLegacy)
|
|
}
|
|
|
|
override fun resolve(connection: ServerConnection): WorldID {
|
|
return worldID
|
|
}
|
|
|
|
override fun toString(): String {
|
|
if (target != SpawnTarget.Whatever)
|
|
return "$worldID=$target"
|
|
|
|
return "$worldID"
|
|
}
|
|
}
|
|
|
|
data class Player(val uuid: UUID) : WarpAction() {
|
|
override fun write(stream: DataOutputStream, isLegacy: Boolean) {
|
|
stream.writeByte(2)
|
|
stream.writeUUID(uuid)
|
|
}
|
|
|
|
override fun resolve(connection: ServerConnection): WorldID {
|
|
if (connection.uuid == uuid)
|
|
return connection.world?.worldID ?: WorldID.Limbo
|
|
|
|
return connection.server.clientByUUID(uuid)?.world?.worldID ?: WorldID.Limbo
|
|
}
|
|
|
|
override fun toString(): String {
|
|
return "Player:$uuid"
|
|
}
|
|
}
|
|
|
|
class Adapter(gson: Gson) : TypeAdapter<WarpAction>() {
|
|
override fun write(out: JsonWriter, value: WarpAction) {
|
|
out.value(value.toString())
|
|
}
|
|
|
|
override fun read(`in`: JsonReader): WarpAction {
|
|
return parse(`in`.nextString())
|
|
}
|
|
}
|
|
|
|
companion object {
|
|
fun read(stream: DataInputStream, isLegacy: Boolean): WarpAction {
|
|
return when (val type = stream.readUnsignedByte()) {
|
|
1 -> World(WorldID.read(stream, isLegacy), SpawnTarget.read(stream, isLegacy))
|
|
2 -> Player(stream.readUUID())
|
|
3 -> {
|
|
when (val type2 = stream.readInt()) {
|
|
0 -> WarpAlias.Return
|
|
1 -> WarpAlias.OrbitedWorld
|
|
2 -> WarpAlias.OwnShip
|
|
else -> throw IllegalArgumentException("Unknown WarpAlias type $type2!")
|
|
}
|
|
}
|
|
|
|
else -> throw IllegalArgumentException("Unknown WarpAction type $type!")
|
|
}
|
|
}
|
|
|
|
fun parse(value: String): WarpAction {
|
|
if (value.lowercase() == "return") {
|
|
return WarpAlias.Return
|
|
} else if (value.lowercase() == "orbitedworld") {
|
|
return WarpAlias.OrbitedWorld
|
|
} else if (value.lowercase().startsWith("player:")) {
|
|
return Player(UUID.fromString(value.substring(7)))
|
|
} else {
|
|
val parts = value.split('=')
|
|
val world = WorldID.parse(parts[0])
|
|
var spawnTarget: SpawnTarget = SpawnTarget.Whatever
|
|
|
|
if (parts.size == 2) {
|
|
spawnTarget = SpawnTarget.parse(parts[1])
|
|
}
|
|
|
|
return World(world, spawnTarget)
|
|
}
|
|
}
|
|
|
|
val CODEC = nativeCodec(::read, WarpAction::write)
|
|
val LEGACY_CODEC = legacyCodec(::read, WarpAction::write)
|
|
}
|
|
}
|
|
|
|
sealed class WarpAlias(val index: Int) : WarpAction() {
|
|
final override fun write(stream: DataOutputStream, isLegacy: Boolean) {
|
|
stream.write(3)
|
|
// because it is defined as enum class WarpAlias, without specifying uint8_t as type
|
|
stream.writeInt(index)
|
|
}
|
|
|
|
abstract fun remap(connection: ServerConnection): WarpAction
|
|
|
|
final override fun resolve(connection: ServerConnection): WorldID {
|
|
throw RuntimeException("Trying to use WarpAlias as regular warp action")
|
|
}
|
|
|
|
object Return : WarpAlias(0) {
|
|
override fun remap(connection: ServerConnection): WarpAction {
|
|
return connection.returnWarp ?: World(connection.shipWorld.worldID)
|
|
}
|
|
|
|
override fun toString(): String {
|
|
return "Return"
|
|
}
|
|
}
|
|
|
|
object OrbitedWorld : WarpAlias(1) {
|
|
override fun remap(connection: ServerConnection): WarpAction {
|
|
return connection.orbitalWarpAction.orNull()?.first ?: World(connection.shipWorld.worldID)
|
|
}
|
|
|
|
override fun toString(): String {
|
|
return "OrbitedWorld"
|
|
}
|
|
}
|
|
|
|
object OwnShip : WarpAlias(2) {
|
|
override fun remap(connection: ServerConnection): WarpAction {
|
|
return World(connection.shipWorld.worldID)
|
|
}
|
|
|
|
override fun toString(): String {
|
|
return "OwnShip"
|
|
}
|
|
}
|
|
}
|
|
|
|
enum class WarpMode(override val jsonName: String) : IStringSerializable {
|
|
NONE("None"),
|
|
BEAM_ONLY("BeamOnly"),
|
|
DEPLOY_ONLY("DeployOnly"),
|
|
BEAM_OR_DEPLOY("BeamOrDeploy");
|
|
|
|
companion object {
|
|
val CODEC = StreamCodec.Enum(WarpMode::class.java)
|
|
}
|
|
}
|