package ru.dbotthepony.kstarbound.defs import ru.dbotthepony.kommons.io.StreamCodec import ru.dbotthepony.kommons.io.readUUID import ru.dbotthepony.kommons.io.readVector2d import ru.dbotthepony.kommons.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.kommons.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 // original game has MVariant here // MVariant prepends InvalidValue to Variant<> template // Variant<> itself works with LookupTypeIndex // 0 is responsible for holding current template comparison check // typedef MVariant WarpAction; // -> Variant WarpAction // hence WarpToWorld has index 1, WarpToPlayer 2, WarpAlias 3 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 "SpawnTarget.SpawnTarget" } } 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 == id }?.position } override fun toString(): String { return "SpawnTarget.Entity[$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 "SpawnTarget.Position[$position]" } 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 "SpawnTarget.X[$position]" } override fun resolve(world: ServerWorld): Vector2d { TODO("Not yet implemented") } } companion object { 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!") } } } } 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) : 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 { TODO("Not yet implemented") } } 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 } } 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!") } } val CODEC = nativeCodec(::read, WarpAction::write) val LEGACY_CODEC = legacyCodec(::read, WarpAction::write) } } sealed class WarpAlias(val index: Int) : WarpAction() { 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) } object Return : WarpAlias(0) { override fun resolve(connection: ServerConnection): WorldID { TODO("Not yet implemented") } override fun toString(): String { return "WarpAlias.Return" } } object OrbitedWorld : WarpAlias(1) { override fun resolve(connection: ServerConnection): WorldID { TODO("Not yet implemented") } override fun toString(): String { return "WarpAlias.OrbitedWorld" } } object OwnShip : WarpAlias(2) { override fun resolve(connection: ServerConnection): WorldID { return connection.shipWorld.worldID } override fun toString(): String { return "WarpAlias.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) } }