biggest refactor ever...
- use kxs-ts-gen for TypeScript schemas
- update schema files, so they're tidier (no need for typescript-generator workarounds)
- version bumps (kotlin, gradle, tstl, factorio, kotest)
- improve kxs binary format (use okio)
- move schema files package ('events' wasn't needed)
- tidy up topic creation, naming. base it off classes, make util functions
This commit is contained in:
parent
db14865ca9
commit
3fff57d46d
82 changed files with 2545 additions and 1914 deletions
|
|
@ -1,5 +1,6 @@
|
|||
import dev.adamko.kafkatorio.gradle.asProvider
|
||||
import dev.adamko.kafkatorio.gradle.typescriptAttributes
|
||||
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
|
||||
|
||||
|
||||
plugins {
|
||||
|
|
@ -11,6 +12,7 @@ plugins {
|
|||
|
||||
|
||||
kotlin {
|
||||
|
||||
js(IR) {
|
||||
binaries.executable()
|
||||
// browser{} causes this error:
|
||||
|
|
@ -32,6 +34,11 @@ kotlin {
|
|||
nodejs()
|
||||
}
|
||||
jvm {
|
||||
val main by compilations.getting {
|
||||
kotlinOptions {
|
||||
jvmTarget = "11"
|
||||
}
|
||||
}
|
||||
testRuns["test"].executionTask.configure {
|
||||
useJUnitPlatform()
|
||||
}
|
||||
|
|
@ -56,6 +63,10 @@ kotlin {
|
|||
implementation(project.dependencies.platform(libs.kotlinx.serialization.bom))
|
||||
implementation(libs.kotlinx.serialization.core)
|
||||
implementation(libs.kotlinx.serialization.json)
|
||||
api("dev.adamko.kxtsgen:kxs-ts-gen-core:dev-SNAPSHOT")
|
||||
// implementation("com.github.adamko-dev:kotlinx-serialization-typescript-generator:dev-SNAPSHOT") {
|
||||
// isChanging = true
|
||||
// }
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -76,7 +87,9 @@ kotlin {
|
|||
dependencies {
|
||||
implementation("com.github.aSemy:ts-generator:v1.2.1")
|
||||
// implementation("com.github.ntrrgc:ts-generator:1.1.2")
|
||||
implementation("org.jetbrains.kotlin:kotlin-reflect")
|
||||
implementation(kotlin("reflect"))
|
||||
|
||||
// implementation("com.github.adamko-dev:kotlinx-serialization-typescript-generator:0.0.5")
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -105,7 +118,8 @@ val generateTypescript by tasks.registering(JavaExec::class) {
|
|||
main.runtimeDependencyFiles
|
||||
)
|
||||
|
||||
mainClass.set("dev.adamko.kafkatorio.events.schema.Kt2tsKt")
|
||||
// mainClass.set("dev.adamko.kafkatorio.events.schema.Kt2tsKt")
|
||||
mainClass.set("dev.adamko.kafkatorio.events.schema.Kt2ts2Kt")
|
||||
|
||||
val tempOutputDirectory = file("$temporaryDir")
|
||||
args(tempOutputDirectory)
|
||||
|
|
|
|||
|
|
@ -1,57 +0,0 @@
|
|||
package dev.adamko.kafkatorio.events.schema.converters
|
||||
|
||||
import dev.adamko.kafkatorio.events.schema.MAP_CHUNK_SIZE
|
||||
import dev.adamko.kafkatorio.events.schema.MapChunkPosition
|
||||
import dev.adamko.kafkatorio.events.schema.MapEntityPosition
|
||||
import dev.adamko.kafkatorio.events.schema.MapTilePosition
|
||||
import kotlin.math.floor
|
||||
|
||||
operator fun MapTilePosition.times(factor: Int) =
|
||||
MapTilePosition(x * factor, y * factor)
|
||||
|
||||
operator fun MapTilePosition.div(divisor: Int) =
|
||||
MapTilePosition(x / divisor, y / divisor)
|
||||
|
||||
operator fun MapTilePosition.plus(addend: Int) =
|
||||
MapTilePosition(x + addend, y + addend)
|
||||
|
||||
|
||||
fun MapTilePosition.toMapChunkPosition(chunkSize: Int = MAP_CHUNK_SIZE) =
|
||||
MapChunkPosition(
|
||||
floor(x.toDouble() / chunkSize.toDouble()).toInt(),
|
||||
floor(y.toDouble() / chunkSize.toDouble()).toInt(),
|
||||
)
|
||||
|
||||
|
||||
fun MapEntityPosition.toMapTilePosition() =
|
||||
MapChunkPosition(floor(x).toInt(), floor(y).toInt())
|
||||
|
||||
fun MapEntityPosition.toMapChunkPosition(chunkSize: Int = MAP_CHUNK_SIZE) =
|
||||
MapChunkPosition(
|
||||
floor(x / chunkSize.toDouble()).toInt(),
|
||||
floor(y / chunkSize.toDouble()).toInt(),
|
||||
)
|
||||
|
||||
|
||||
operator fun MapChunkPosition.times(factor: Int) =
|
||||
MapChunkPosition(x * factor, y * factor)
|
||||
|
||||
operator fun MapChunkPosition.plus(addend: Int) =
|
||||
MapChunkPosition(x + addend, y + addend)
|
||||
|
||||
operator fun MapChunkPosition.minus(subtrahend: Int) =
|
||||
MapChunkPosition(x - subtrahend, y - subtrahend)
|
||||
|
||||
|
||||
fun MapChunkPosition.leftTopTile(chunkSize: Int = MAP_CHUNK_SIZE): MapTilePosition =
|
||||
MapTilePosition(
|
||||
x * chunkSize,
|
||||
y * chunkSize,
|
||||
)
|
||||
|
||||
//val MapChunkPosition.rightBottomTile: MapTilePosition
|
||||
// get() = leftTopTile + (MAP_CHUNK_SIZE - 1)
|
||||
//
|
||||
//
|
||||
//val MapChunkPosition.boundingBox: MapBoundingBox
|
||||
// get() = MapBoundingBox(leftTopTile, rightBottomTile)
|
||||
|
|
@ -1,158 +0,0 @@
|
|||
package dev.adamko.kafkatorio.events.schema
|
||||
|
||||
import kotlin.jvm.JvmInline
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
|
||||
const val MAP_CHUNK_SIZE = 32
|
||||
|
||||
|
||||
@Serializable
|
||||
@JvmInline
|
||||
value class Tick(val value: UInt)
|
||||
|
||||
|
||||
/**
|
||||
* Coordinates of a [EntityData] on a map.
|
||||
*
|
||||
* A [MapEntityPosition] can be translated to a [MapChunkPosition] by dividing the `x`/`y` values
|
||||
* by [32][MAP_CHUNK_SIZE].
|
||||
*/
|
||||
@Serializable
|
||||
data class MapEntityPosition(
|
||||
val x: Double,
|
||||
val y: Double,
|
||||
)
|
||||
|
||||
|
||||
/** Coordinates of a chunk in a [SurfaceData] where each integer `x`/`y` represents a different
|
||||
* chunk.
|
||||
*
|
||||
* A [MapChunkPosition] can be translated to a [MapEntityPosition] by multiplying the `x`/`y`
|
||||
* values by [32][MAP_CHUNK_SIZE].
|
||||
*/
|
||||
@Serializable
|
||||
data class MapChunkPosition(
|
||||
val x: Int,
|
||||
val y: Int,
|
||||
)
|
||||
|
||||
|
||||
/**
|
||||
* Coordinates of a tile in a chunk on a [SurfaceData] where each integer `x`/`y` represents a
|
||||
* different [MapTile].
|
||||
*
|
||||
* It rounds any `x`/`y` down to whole numbers.
|
||||
*/
|
||||
@Serializable
|
||||
data class MapTilePosition(
|
||||
val x: Int,
|
||||
val y: Int,
|
||||
)
|
||||
|
||||
|
||||
@Serializable
|
||||
data class MapBoundingBox(
|
||||
val topLeft: MapTilePosition,
|
||||
val bottomRight: MapTilePosition,
|
||||
)
|
||||
|
||||
|
||||
/**
|
||||
* Red, green, blue and alpha values, all in range `[0, 1]` or all in range `[0, 255]` if any
|
||||
* value is > 1.
|
||||
*
|
||||
* All values here are optional. Colour channels default to `0`, the alpha channel defaults to `1`.
|
||||
*/
|
||||
@Serializable
|
||||
data class Colour(
|
||||
val red: Float = 0f,
|
||||
val green: Float = 0f,
|
||||
val blue: Float = 0f,
|
||||
val alpha: Float = 1f,
|
||||
)
|
||||
|
||||
|
||||
/** Size-efficient version of [Colour] (`4*4` bytes vs `4*1` bytes) */
|
||||
@Serializable
|
||||
data class ColourHex(
|
||||
val red: UByte = UByte.MIN_VALUE,
|
||||
val green: UByte = UByte.MIN_VALUE,
|
||||
val blue: UByte = UByte.MIN_VALUE,
|
||||
val alpha: UByte = UByte.MAX_VALUE,
|
||||
) {
|
||||
companion object {
|
||||
val TRANSPARENT = ColourHex(0u, 0u, 0u, 0u)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Serializable
|
||||
data class MapTiles(
|
||||
val surfaceIndex: SurfaceIndex,
|
||||
val tiles: List<MapTile>,
|
||||
)
|
||||
|
||||
|
||||
@Serializable
|
||||
data class MapTile(
|
||||
val x: Int,
|
||||
val y: Int,
|
||||
val proto: PrototypeName,
|
||||
)
|
||||
|
||||
|
||||
interface EntityIdentifiers {
|
||||
val unitNumber: UnitNumber?
|
||||
val name: String
|
||||
val type: String
|
||||
}
|
||||
|
||||
|
||||
// instant of EntityIdentifiers, because kxs needs a concrete instance
|
||||
@Serializable
|
||||
data class EntityIdentifiersData(
|
||||
override val unitNumber: UnitNumber? = null,
|
||||
override val name: String,
|
||||
/** The prototype-type of the entity. */
|
||||
override val type: String,
|
||||
) : EntityIdentifiers
|
||||
|
||||
|
||||
/**
|
||||
* Size-optimised 2D map of x/y tile positions and prototype-name.
|
||||
*
|
||||
* Each X/Y coordinate maps to an arbitrary [PrototypeKey] (which is only valid for any specific
|
||||
* instance). [protos] is specific to each instance of [MapTileDictionary], can be used to convert from a
|
||||
*/
|
||||
@Serializable
|
||||
data class MapTileDictionary(
|
||||
/** Map an X,Y coordinate a prototype name */
|
||||
val tilesXY: Map<String, Map<String, PrototypeKey>>,
|
||||
val protos: Map<PrototypeKey, PrototypeName>,
|
||||
) {
|
||||
|
||||
fun toMapTileList(): List<MapTile> = buildList {
|
||||
tilesXY.forEach { (xString, row) ->
|
||||
row.forEach { (yString, protoIndex) ->
|
||||
val x = xString.toIntOrNull()
|
||||
val y = yString.toIntOrNull()
|
||||
val protoName = protos[protoIndex]
|
||||
if (x != null && y != null && protoName != null) {
|
||||
add(MapTile(x, y, protoName))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
fun MapTileDictionary?.isNullOrEmpty(): Boolean {
|
||||
return this == null || (tilesXY.isEmpty() && tilesXY.all { it.value.isEmpty() })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Serializable
|
||||
@JvmInline
|
||||
value class PrototypeKey(val index: String)
|
||||
|
|
@ -1,287 +0,0 @@
|
|||
package dev.adamko.kafkatorio.events.schema
|
||||
|
||||
import dev.adamko.kafkatorio.events.schema.FactorioUpdateData.FactorioUpdateDataType
|
||||
import kotlinx.serialization.DeserializationStrategy
|
||||
import kotlinx.serialization.EncodeDefault
|
||||
import kotlinx.serialization.Serializable
|
||||
import kotlinx.serialization.json.JsonContentPolymorphicSerializer
|
||||
import kotlinx.serialization.json.JsonElement
|
||||
import kotlinx.serialization.json.contentOrNull
|
||||
import kotlinx.serialization.json.jsonObject
|
||||
import kotlinx.serialization.json.jsonPrimitive
|
||||
|
||||
|
||||
@Serializable
|
||||
sealed class KafkatorioPacket {
|
||||
/** Schema versioning */
|
||||
abstract val modVersion: String
|
||||
abstract val tick: Tick
|
||||
abstract val update: FactorioUpdateData
|
||||
}
|
||||
|
||||
|
||||
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
|
||||
|
||||
|
||||
sealed interface FactorioUpdateDataKey {
|
||||
val updateType: FactorioUpdateDataType
|
||||
}
|
||||
|
||||
|
||||
@Serializable(with = FactorioUpdateData.JsonSerializer::class)
|
||||
sealed class FactorioUpdateData : FactorioUpdateDataKey {
|
||||
|
||||
/** Discriminator for [FactorioUpdateData] */
|
||||
@EncodeDefault
|
||||
abstract override val updateType: FactorioUpdateDataType
|
||||
|
||||
/** the initial Factorio event (`defines.events`) trigger */
|
||||
abstract val eventCounts: Map<String, UInt>?
|
||||
|
||||
@Serializable
|
||||
enum class FactorioUpdateDataType {
|
||||
PLAYER,
|
||||
MAP_CHUNK,
|
||||
ENTITY,
|
||||
CONSOLE_CHAT,
|
||||
CONSOLE_COMMAND,
|
||||
SURFACE,
|
||||
CONFIG,
|
||||
PROTOTYPES,
|
||||
;
|
||||
|
||||
companion object {
|
||||
val values: List<FactorioUpdateDataType> = values().toList()
|
||||
}
|
||||
}
|
||||
|
||||
object JsonSerializer : JsonContentPolymorphicSerializer<FactorioUpdateData>(
|
||||
FactorioUpdateData::class
|
||||
) {
|
||||
private val key = FactorioUpdateData::updateType.name
|
||||
|
||||
override fun selectDeserializer(element: JsonElement): DeserializationStrategy<out FactorioUpdateData> {
|
||||
|
||||
val type = element
|
||||
.jsonObject[key]
|
||||
?.jsonPrimitive
|
||||
?.contentOrNull
|
||||
?.let { json ->
|
||||
FactorioUpdateDataType.values.firstOrNull { it.name == json }
|
||||
}
|
||||
|
||||
requireNotNull(type) { "Unknown FactorioEventUpdate $key: $element" }
|
||||
|
||||
return when (type) {
|
||||
FactorioUpdateDataType.PLAYER -> PlayerUpdate.serializer()
|
||||
FactorioUpdateDataType.MAP_CHUNK -> MapChunkUpdate.serializer()
|
||||
FactorioUpdateDataType.ENTITY -> EntityUpdate.serializer()
|
||||
FactorioUpdateDataType.CONSOLE_CHAT -> ConsoleChatUpdate.serializer()
|
||||
FactorioUpdateDataType.CONSOLE_COMMAND -> ConsoleCommandUpdate.serializer()
|
||||
FactorioUpdateDataType.SURFACE -> SurfaceUpdate.serializer()
|
||||
FactorioUpdateDataType.CONFIG -> ConfigurationUpdate.serializer()
|
||||
FactorioUpdateDataType.PROTOTYPES -> PrototypesUpdate.serializer()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
|
||||
|
||||
|
||||
sealed interface PlayerUpdateKey : FactorioUpdateDataKey {
|
||||
val index: PlayerIndex
|
||||
}
|
||||
|
||||
|
||||
@Serializable
|
||||
data class PlayerUpdate(
|
||||
override val index: PlayerIndex,
|
||||
|
||||
override val eventCounts: Map<String, UInt>? = null,
|
||||
|
||||
val characterUnitNumber: UnitNumber? = null,
|
||||
val chatColour: Colour? = null,
|
||||
val colour: Colour? = null,
|
||||
val name: String? = null,
|
||||
|
||||
val afkTime: Tick? = null,
|
||||
val ticksToRespawn: Tick? = null,
|
||||
val forceIndex: ForceIndex? = null,
|
||||
val isAdmin: Boolean? = null,
|
||||
val isConnected: Boolean? = null,
|
||||
val isShowOnMap: Boolean? = null,
|
||||
val isSpectator: Boolean? = null,
|
||||
val lastOnline: Tick? = null,
|
||||
val onlineTime: Tick? = null,
|
||||
val position: MapEntityPosition? = null,
|
||||
val surfaceIndex: SurfaceIndex? = null,
|
||||
val tag: String? = null,
|
||||
val diedCause: EntityIdentifiersData? = null,
|
||||
|
||||
val bannedReason: String? = null,
|
||||
val kickedReason: String? = null,
|
||||
val disconnectReason: String? = null,
|
||||
/** `true` when a player is removed (deleted) from the game */
|
||||
val isRemoved: Boolean? = null,
|
||||
) : FactorioUpdateData(), PlayerUpdateKey {
|
||||
@EncodeDefault
|
||||
override val updateType: FactorioUpdateDataType = FactorioUpdateDataType.PLAYER
|
||||
}
|
||||
|
||||
|
||||
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
|
||||
|
||||
|
||||
sealed interface EntityUpdateKey : EntityIdentifiers, FactorioUpdateDataKey {
|
||||
/** A [unitNumber] is required for caching */
|
||||
override val unitNumber: UnitNumber
|
||||
override val name: String
|
||||
override val type: String
|
||||
}
|
||||
|
||||
|
||||
@Serializable
|
||||
data class EntityUpdate(
|
||||
override val unitNumber: UnitNumber,
|
||||
override val name: String,
|
||||
override val type: String,
|
||||
|
||||
override val eventCounts: Map<String, UInt>? = null,
|
||||
|
||||
val chunkPosition: MapChunkPosition? = null,
|
||||
val graphicsVariation: UByte? = null,
|
||||
val health: Float? = null,
|
||||
val isActive: Boolean? = null,
|
||||
val isRotatable: Boolean? = null,
|
||||
val lastUser: UInt? = null,
|
||||
val localisedDescription: String? = null,
|
||||
val localisedName: String? = null,
|
||||
val prototype: PrototypeName? = null,
|
||||
) : FactorioUpdateData(), EntityUpdateKey {
|
||||
@EncodeDefault
|
||||
override val updateType: FactorioUpdateDataType = FactorioUpdateDataType.ENTITY
|
||||
}
|
||||
|
||||
|
||||
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
|
||||
|
||||
|
||||
sealed interface MapChunkUpdateKey : FactorioUpdateDataKey {
|
||||
val chunkPosition: MapChunkPosition
|
||||
val surfaceIndex: SurfaceIndex
|
||||
}
|
||||
|
||||
|
||||
@Serializable
|
||||
data class MapChunkUpdate(
|
||||
override val chunkPosition: MapChunkPosition,
|
||||
override val surfaceIndex: SurfaceIndex,
|
||||
|
||||
override val eventCounts: Map<String, UInt>? = null,
|
||||
|
||||
val player: PlayerIndex? = null,
|
||||
val robot: EntityIdentifiersData? = null,
|
||||
val force: ForceIndex? = null,
|
||||
/** updated tiles - might be partial and not all tiles in the chunk */
|
||||
val tileDictionary: MapTileDictionary? = null,
|
||||
val isDeleted: Boolean? = null,
|
||||
) : FactorioUpdateData(), MapChunkUpdateKey {
|
||||
@EncodeDefault
|
||||
override val updateType: FactorioUpdateDataType = FactorioUpdateDataType.MAP_CHUNK
|
||||
}
|
||||
|
||||
|
||||
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
|
||||
|
||||
|
||||
@Serializable
|
||||
data class ConsoleChatUpdate(
|
||||
override val eventCounts: Map<String, UInt>? = null,
|
||||
|
||||
val authorPlayerIndex: PlayerIndex?,
|
||||
val content: String,
|
||||
) : FactorioUpdateData() {
|
||||
@EncodeDefault
|
||||
override val updateType: FactorioUpdateDataType = FactorioUpdateDataType.CONSOLE_CHAT
|
||||
|
||||
}
|
||||
|
||||
@Serializable
|
||||
data class ConsoleCommandUpdate(
|
||||
override val eventCounts: Map<String, UInt>? = null,
|
||||
|
||||
val authorPlayerIndex: PlayerIndex?,
|
||||
val command: String,
|
||||
val parameters: String,
|
||||
) : FactorioUpdateData() {
|
||||
@EncodeDefault
|
||||
override val updateType: FactorioUpdateDataType = FactorioUpdateDataType.CONSOLE_COMMAND
|
||||
}
|
||||
|
||||
|
||||
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
|
||||
|
||||
|
||||
sealed interface SurfaceUpdateKey : FactorioUpdateDataKey {
|
||||
val index: SurfaceIndex
|
||||
}
|
||||
|
||||
@Serializable
|
||||
data class SurfaceUpdate(
|
||||
override val index: SurfaceIndex,
|
||||
|
||||
override val eventCounts: Map<String, UInt>? = null,
|
||||
|
||||
val daytime: Double,
|
||||
val name: String,
|
||||
) : FactorioUpdateData(), SurfaceUpdateKey {
|
||||
@EncodeDefault
|
||||
override val updateType: FactorioUpdateDataType = FactorioUpdateDataType.SURFACE
|
||||
}
|
||||
|
||||
|
||||
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
|
||||
|
||||
|
||||
@Serializable
|
||||
data class ConfigurationUpdate(
|
||||
override val eventCounts: Map<String, UInt>? = null,
|
||||
|
||||
val factorioData: GameData,
|
||||
val allMods: List<ModData>,
|
||||
) : FactorioUpdateData() {
|
||||
@EncodeDefault
|
||||
override val updateType: FactorioUpdateDataType = FactorioUpdateDataType.CONFIG
|
||||
|
||||
@Serializable
|
||||
data class GameData(
|
||||
val oldVersion: String? = null,
|
||||
val newVersion: String? = null,
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class ModData(
|
||||
val modName: String,
|
||||
val currentVersion: String? = null,
|
||||
val previousVersion: String? = null,
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
|
||||
|
||||
|
||||
@Serializable
|
||||
data class PrototypesUpdate(
|
||||
override val eventCounts: Map<String, UInt>? = null,
|
||||
|
||||
val prototypes: List<FactorioPrototype>,
|
||||
) : FactorioUpdateData() {
|
||||
@EncodeDefault
|
||||
override val updateType: FactorioUpdateDataType = FactorioUpdateDataType.PROTOTYPES
|
||||
}
|
||||
|
||||
|
||||
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
|
||||
|
|
@ -1,8 +1,38 @@
|
|||
package dev.adamko.kafkatorio.events.schema.converters
|
||||
package dev.adamko.kafkatorio.schema.common
|
||||
|
||||
import dev.adamko.kafkatorio.events.schema.Colour
|
||||
import dev.adamko.kafkatorio.events.schema.ColourHex
|
||||
import kotlin.math.roundToInt
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
/**
|
||||
* Red, green, blue and alpha values, all in range `[0, 1]` or all in range `[0, 255]` if any
|
||||
* value is > 1.
|
||||
*
|
||||
* All values here are optional. Colour channels default to `0`, the alpha channel defaults to `1`.
|
||||
*/
|
||||
@Serializable
|
||||
data class Colour(
|
||||
val red: Float = 0f,
|
||||
val green: Float = 0f,
|
||||
val blue: Float = 0f,
|
||||
val alpha: Float = 1f,
|
||||
)
|
||||
|
||||
|
||||
/** Size-efficient version of [Colour] (`4*4` bytes vs `4*1` bytes) */
|
||||
@Serializable
|
||||
data class ColourHex(
|
||||
val red: UByte = UByte.MIN_VALUE,
|
||||
val green: UByte = UByte.MIN_VALUE,
|
||||
val blue: UByte = UByte.MIN_VALUE,
|
||||
val alpha: UByte = UByte.MAX_VALUE,
|
||||
) {
|
||||
companion object {
|
||||
val TRANSPARENT = ColourHex(0u, 0u, 0u, 0u)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
|
||||
|
||||
|
||||
/** True if any value is greater than 1, so the values are hexadecimal. */
|
||||
|
|
@ -74,8 +104,10 @@ fun Colour.toHex(): ColourHex {
|
|||
)
|
||||
}
|
||||
|
||||
|
||||
private fun UByte.toPercentileFloat() = (this.toFloat() / ubyteMaxFloat).coerceIn(0f..1f)
|
||||
|
||||
|
||||
fun ColourHex.toPercentile(): Colour {
|
||||
return Colour(
|
||||
red.toPercentileFloat(),
|
||||
|
|
@ -1,9 +1,14 @@
|
|||
package dev.adamko.kafkatorio.events.schema
|
||||
package dev.adamko.kafkatorio.schema.common
|
||||
|
||||
import kotlin.jvm.JvmInline
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
|
||||
@Serializable
|
||||
@JvmInline
|
||||
value class Tick(val value: UInt)
|
||||
|
||||
|
||||
/**
|
||||
* Unique ID associated with this surface
|
||||
*
|
||||
|
|
@ -44,3 +49,20 @@ value class UnitNumber(private val id: UInt) {
|
|||
value class PrototypeName(private val id: String) {
|
||||
override fun toString(): String = id
|
||||
}
|
||||
|
||||
|
||||
interface EntityIdentifiers {
|
||||
val unitNumber: UnitNumber?
|
||||
val name: String
|
||||
/** The prototype-type of the entity. */
|
||||
val protoType: String
|
||||
}
|
||||
|
||||
|
||||
// instant of EntityIdentifiers, because kxs needs a concrete instance
|
||||
@Serializable
|
||||
data class EntityIdentifiersData(
|
||||
override val unitNumber: UnitNumber? = null,
|
||||
override val name: String,
|
||||
override val protoType: String,
|
||||
) : EntityIdentifiers
|
||||
|
|
@ -0,0 +1,75 @@
|
|||
package dev.adamko.kafkatorio.schema.common
|
||||
|
||||
import dev.adamko.kafkatorio.schema.common.MapTileDictionary.PrototypeKey
|
||||
import kotlin.jvm.JvmInline
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
|
||||
const val MAP_CHUNK_SIZE = 32
|
||||
|
||||
@Serializable
|
||||
data class MapTiles(
|
||||
val surfaceIndex: SurfaceIndex,
|
||||
val tiles: List<MapTile>,
|
||||
)
|
||||
|
||||
|
||||
@Serializable
|
||||
data class MapTile(
|
||||
val x: Int,
|
||||
val y: Int,
|
||||
val proto: PrototypeName,
|
||||
)
|
||||
|
||||
|
||||
/**
|
||||
* 2D map of x/y tile positions and prototype-name, optimised for JSON size.
|
||||
*
|
||||
* [protos] is a one-to-one map between a [PrototypeName] and [PrototypeKey].
|
||||
*
|
||||
* [PrototypeKey] is an arbitrary integer value, which maps to one [PrototypeName] only for a
|
||||
* specific instance of [MapTileDictionary].
|
||||
*
|
||||
* Each X/Y coordinate in [tilesXY] maps to a [PrototypeKey].
|
||||
*/
|
||||
@Serializable
|
||||
data class MapTileDictionary(
|
||||
/** Map an X,Y coordinate a prototype name */
|
||||
val tilesXY: Map<String, Map<String, PrototypeKey>>,
|
||||
val protos: Map<PrototypeName, PrototypeKey>,
|
||||
) {
|
||||
private val protosIndexToName: Map<PrototypeKey, PrototypeName> by lazy {
|
||||
protos.entries.associate { (name, key) -> key to name }
|
||||
}
|
||||
|
||||
fun toMapTileList(): List<MapTile> = buildList {
|
||||
tilesXY.forEach { (xString, row) ->
|
||||
row.forEach { (yString, protoIndex) ->
|
||||
val x = xString.toIntOrNull()
|
||||
val y = yString.toIntOrNull()
|
||||
val protoName = protosIndexToName[protoIndex]
|
||||
if (x != null && y != null && protoName != null) {
|
||||
add(MapTile(x, y, protoName))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** The collection-index of a prototype in [MapTileDictionary.protos] */
|
||||
@Serializable
|
||||
@JvmInline
|
||||
value class PrototypeKey(val index: Int)
|
||||
|
||||
companion object {
|
||||
fun MapTileDictionary?.isNullOrEmpty(): Boolean {
|
||||
return this == null || (tilesXY.isEmpty() && tilesXY.all { it.value.isEmpty() })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Serializable
|
||||
data class MapBoundingBox(
|
||||
val topLeft: MapTilePosition,
|
||||
val bottomRight: MapTilePosition,
|
||||
)
|
||||
|
|
@ -0,0 +1,142 @@
|
|||
package dev.adamko.kafkatorio.schema.common
|
||||
|
||||
import dev.adamko.kxstsgen.core.*
|
||||
import dev.adamko.kxstsgen.core.experiments.TupleSerializer
|
||||
import kotlin.math.floor
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
/**
|
||||
* Coordinates of a [EntityData] on a map.
|
||||
*
|
||||
* A [MapEntityPosition] can be translated to a [MapChunkPosition] by dividing the `x`/`y` values
|
||||
* by [32][MAP_CHUNK_SIZE].
|
||||
*/
|
||||
@Serializable(with = MapEntityPosition.Serializer::class)
|
||||
data class MapEntityPosition(
|
||||
val x: Double,
|
||||
val y: Double,
|
||||
) {
|
||||
object Serializer : TupleSerializer<MapEntityPosition>(
|
||||
"MapEntityPosition",
|
||||
{
|
||||
element(MapEntityPosition::x)
|
||||
element(MapEntityPosition::y)
|
||||
}
|
||||
) {
|
||||
override fun tupleConstructor(elements: Iterator<*>): MapEntityPosition {
|
||||
return MapEntityPosition(
|
||||
elements.next() as Double,
|
||||
elements.next() as Double,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/** Coordinates of a chunk in a [SurfaceData] where each integer `x`/`y` represents a different
|
||||
* chunk.
|
||||
*
|
||||
* A [MapChunkPosition] can be translated to a [MapEntityPosition] by multiplying the `x`/`y`
|
||||
* values by [32][MAP_CHUNK_SIZE].
|
||||
*/
|
||||
@Serializable(with = MapChunkPosition.Serializer::class)
|
||||
data class MapChunkPosition(
|
||||
val x: Int,
|
||||
val y: Int,
|
||||
) {
|
||||
object Serializer : TupleSerializer<MapChunkPosition>(
|
||||
"MapChunkPosition",
|
||||
{
|
||||
element(MapChunkPosition::x)
|
||||
element(MapChunkPosition::y)
|
||||
}
|
||||
) {
|
||||
override fun tupleConstructor(elements: Iterator<*>): MapChunkPosition {
|
||||
return MapChunkPosition(
|
||||
elements.next() as Int,
|
||||
elements.next() as Int,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Coordinates of a tile in a chunk on a [SurfaceData] where each integer `x`/`y` represents a
|
||||
* different [MapTile].
|
||||
*
|
||||
* It rounds any `x`/`y` down to whole numbers.
|
||||
*/
|
||||
@Serializable(with = MapTilePosition.Serializer::class)
|
||||
data class MapTilePosition(
|
||||
val x: Int,
|
||||
val y: Int,
|
||||
) {
|
||||
object Serializer : TupleSerializer<MapTilePosition>(
|
||||
"MapTilePosition",
|
||||
{
|
||||
element(MapTilePosition::x)
|
||||
element(MapTilePosition::y)
|
||||
}
|
||||
) {
|
||||
override fun tupleConstructor(elements: Iterator<*>): MapTilePosition {
|
||||
val x = requireNotNull(elements.next() as? Int)
|
||||
val y = requireNotNull(elements.next() as? Int)
|
||||
return MapTilePosition(x, y)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
|
||||
|
||||
|
||||
operator fun MapTilePosition.times(factor: Int) =
|
||||
MapTilePosition(x * factor, y * factor)
|
||||
|
||||
operator fun MapTilePosition.div(divisor: Int) =
|
||||
MapTilePosition(x / divisor, y / divisor)
|
||||
|
||||
operator fun MapTilePosition.plus(addend: Int) =
|
||||
MapTilePosition(x + addend, y + addend)
|
||||
|
||||
|
||||
fun MapTilePosition.toMapChunkPosition(chunkSize: Int = MAP_CHUNK_SIZE) =
|
||||
MapChunkPosition(
|
||||
floor(x.toDouble() / chunkSize.toDouble()).toInt(),
|
||||
floor(y.toDouble() / chunkSize.toDouble()).toInt(),
|
||||
)
|
||||
|
||||
|
||||
fun MapEntityPosition.toMapTilePosition() =
|
||||
MapChunkPosition(floor(x).toInt(), floor(y).toInt())
|
||||
|
||||
fun MapEntityPosition.toMapChunkPosition(chunkSize: Int = MAP_CHUNK_SIZE) =
|
||||
MapChunkPosition(
|
||||
floor(x / chunkSize.toDouble()).toInt(),
|
||||
floor(y / chunkSize.toDouble()).toInt(),
|
||||
)
|
||||
|
||||
|
||||
operator fun MapChunkPosition.times(factor: Int) =
|
||||
MapChunkPosition(x * factor, y * factor)
|
||||
|
||||
operator fun MapChunkPosition.plus(addend: Int) =
|
||||
MapChunkPosition(x + addend, y + addend)
|
||||
|
||||
operator fun MapChunkPosition.minus(subtrahend: Int) =
|
||||
MapChunkPosition(x - subtrahend, y - subtrahend)
|
||||
|
||||
|
||||
fun MapChunkPosition.leftTopTile(chunkSize: Int = MAP_CHUNK_SIZE): MapTilePosition =
|
||||
MapTilePosition(
|
||||
x * chunkSize,
|
||||
y * chunkSize,
|
||||
)
|
||||
|
||||
//val MapChunkPosition.rightBottomTile: MapTilePosition
|
||||
// get() = leftTopTile + (MAP_CHUNK_SIZE - 1)
|
||||
//
|
||||
//
|
||||
//val MapChunkPosition.boundingBox: MapBoundingBox
|
||||
// get() = MapBoundingBox(leftTopTile, rightBottomTile)
|
||||
|
|
@ -0,0 +1,68 @@
|
|||
package dev.adamko.kafkatorio.schema.events
|
||||
|
||||
import dev.adamko.kafkatorio.schema.SerializerProvider
|
||||
import dev.adamko.kafkatorio.schema.SerializerProviderDeserializer
|
||||
import dev.adamko.kafkatorio.schema.common.Tick
|
||||
import kotlin.reflect.KClass
|
||||
import kotlin.reflect.KType
|
||||
import kotlin.reflect.typeOf
|
||||
import kotlinx.serialization.KSerializer
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
|
||||
//@Serializable
|
||||
//data class KafkatorioPacket(
|
||||
// val modVersion: String,
|
||||
// val tick: Tick,
|
||||
// val data: KafkatorioPacketData,
|
||||
//)
|
||||
|
||||
|
||||
@Serializable(with = KafkatorioPacketData.JsonSerializer::class)
|
||||
sealed class KafkatorioPacketData {
|
||||
abstract val updateType: KafkatorioPacketDataType
|
||||
|
||||
object JsonSerializer : SerializerProviderDeserializer<KafkatorioPacketData>(
|
||||
KafkatorioPacketData::class,
|
||||
KafkatorioPacketData::updateType.name,
|
||||
{ json -> KafkatorioPacketDataType.entries.firstOrNull { it.name == json } }
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
/** Discriminator for [KafkatorioPacketData] */
|
||||
@Serializable
|
||||
enum class KafkatorioPacketDataType(
|
||||
override val serializer: KSerializer<out KafkatorioPacketData>
|
||||
) : SerializerProvider<KafkatorioPacketData> {
|
||||
|
||||
// non-keyed
|
||||
CONFIG(ConfigurationUpdate.serializer()),
|
||||
CONSOLE_CHAT(ConsoleChatUpdate.serializer()),
|
||||
CONSOLE_COMMAND(ConsoleCommandUpdate.serializer()),
|
||||
PROTOTYPES(PrototypesUpdate.serializer()),
|
||||
SURFACE(SurfaceUpdate.serializer()),
|
||||
|
||||
// keyed
|
||||
ENTITY(EntityUpdate.serializer()),
|
||||
MAP_CHUNK(MapChunkUpdate.serializer()),
|
||||
PLAYER(PlayerUpdate.serializer()),
|
||||
;
|
||||
|
||||
companion object {
|
||||
val entries: Set<KafkatorioPacketDataType> = values().toSet()
|
||||
}
|
||||
}
|
||||
|
||||
//inline fun <reified T: KafkatorioPacketData> KafkatorioPacketDataType.isType (): KClass<out T> {
|
||||
// return when (this) {
|
||||
// KafkatorioPacketDataType.CONFIG -> ConfigurationUpdate::class
|
||||
// KafkatorioPacketDataType.CONSOLE_CHAT -> ConsoleChatUpdate::class
|
||||
// KafkatorioPacketDataType.CONSOLE_COMMAND -> ConsoleCommandUpdate::class
|
||||
// KafkatorioPacketDataType.PROTOTYPES -> PrototypesUpdate::class
|
||||
// KafkatorioPacketDataType.SURFACE -> SurfaceUpdate::class
|
||||
// KafkatorioPacketDataType.ENTITY -> EntityUpdate::class
|
||||
// KafkatorioPacketDataType.MAP_CHUNK -> MapChunkUpdate::class
|
||||
// KafkatorioPacketDataType.PLAYER -> PlayerUpdate::class
|
||||
// }
|
||||
//}
|
||||
|
|
@ -0,0 +1,95 @@
|
|||
package dev.adamko.kafkatorio.schema.events
|
||||
|
||||
import dev.adamko.kafkatorio.schema.common.PlayerIndex
|
||||
import dev.adamko.kafkatorio.schema.common.SurfaceIndex
|
||||
import dev.adamko.kafkatorio.schema.prototypes.FactorioPrototype
|
||||
import kotlinx.serialization.EncodeDefault
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
|
||||
/** No key, no caching. Must be sent instantly. */
|
||||
sealed class KafkatorioInstantPacketData : KafkatorioPacketData()
|
||||
|
||||
|
||||
/* *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** */
|
||||
|
||||
|
||||
@Serializable
|
||||
data class ConfigurationUpdate(
|
||||
val factorioData: ConfigurationUpdateGameData,
|
||||
val allMods: List<ConfigurationUpdateModData>,
|
||||
val modStartupSettingsChange: Boolean,
|
||||
val migrationApplied: Boolean,
|
||||
) : KafkatorioInstantPacketData() {
|
||||
@EncodeDefault
|
||||
override val updateType: KafkatorioPacketDataType = KafkatorioPacketDataType.CONFIG
|
||||
|
||||
@Serializable
|
||||
data class ConfigurationUpdateGameData(
|
||||
val oldVersion: String? = null,
|
||||
val newVersion: String? = null,
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class ConfigurationUpdateModData(
|
||||
val modName: String,
|
||||
val currentVersion: String? = null,
|
||||
val previousVersion: String? = null,
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
/* *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** */
|
||||
|
||||
|
||||
@Serializable
|
||||
data class ConsoleChatUpdate(
|
||||
val authorPlayerIndex: PlayerIndex?,
|
||||
val content: String,
|
||||
) : KafkatorioInstantPacketData() {
|
||||
@EncodeDefault
|
||||
override val updateType: KafkatorioPacketDataType = KafkatorioPacketDataType.CONSOLE_CHAT
|
||||
}
|
||||
|
||||
|
||||
/* *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** */
|
||||
|
||||
|
||||
@Serializable
|
||||
data class ConsoleCommandUpdate(
|
||||
val authorPlayerIndex: PlayerIndex?,
|
||||
val command: String,
|
||||
val parameters: String,
|
||||
) : KafkatorioInstantPacketData() {
|
||||
@EncodeDefault
|
||||
override val updateType: KafkatorioPacketDataType = KafkatorioPacketDataType.CONSOLE_COMMAND
|
||||
}
|
||||
|
||||
|
||||
/* *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** */
|
||||
|
||||
|
||||
@Serializable
|
||||
data class PrototypesUpdate(
|
||||
val prototypes: List<FactorioPrototype>,
|
||||
) : KafkatorioInstantPacketData() {
|
||||
@EncodeDefault
|
||||
override val updateType: KafkatorioPacketDataType = KafkatorioPacketDataType.PROTOTYPES
|
||||
}
|
||||
|
||||
|
||||
/* *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** */
|
||||
|
||||
|
||||
@Serializable
|
||||
data class SurfaceUpdate(
|
||||
val index: SurfaceIndex,
|
||||
val daytime: Double,
|
||||
val name: String,
|
||||
) : KafkatorioInstantPacketData() {
|
||||
@EncodeDefault
|
||||
override val updateType: KafkatorioPacketDataType = KafkatorioPacketDataType.SURFACE
|
||||
}
|
||||
|
||||
|
||||
/* *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** */
|
||||
|
|
@ -0,0 +1,135 @@
|
|||
package dev.adamko.kafkatorio.schema.events
|
||||
|
||||
import dev.adamko.kafkatorio.schema.common.Colour
|
||||
import dev.adamko.kafkatorio.schema.common.EntityIdentifiers
|
||||
import dev.adamko.kafkatorio.schema.common.EntityIdentifiersData
|
||||
import dev.adamko.kafkatorio.schema.common.ForceIndex
|
||||
import dev.adamko.kafkatorio.schema.common.MapChunkPosition
|
||||
import dev.adamko.kafkatorio.schema.common.MapEntityPosition
|
||||
import dev.adamko.kafkatorio.schema.common.MapTileDictionary
|
||||
import dev.adamko.kafkatorio.schema.common.PlayerIndex
|
||||
import dev.adamko.kafkatorio.schema.common.PrototypeName
|
||||
import dev.adamko.kafkatorio.schema.common.SurfaceIndex
|
||||
import dev.adamko.kafkatorio.schema.common.Tick
|
||||
import dev.adamko.kafkatorio.schema.common.UnitNumber
|
||||
import kotlinx.serialization.EncodeDefault
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
|
||||
sealed interface KafkatorioKeyedPacketKey
|
||||
|
||||
|
||||
/** Has a key, so it can be debounced */
|
||||
sealed class KafkatorioKeyedPacketData : KafkatorioPacketData() {
|
||||
/** Count how many events this packet is aggregating data from */
|
||||
abstract val eventCounts: Map<String, UInt>?
|
||||
}
|
||||
|
||||
|
||||
/* *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** */
|
||||
|
||||
|
||||
sealed interface EntityUpdateKey : EntityIdentifiers, KafkatorioKeyedPacketKey {
|
||||
/** A [unitNumber] is required for caching */
|
||||
override val unitNumber: UnitNumber
|
||||
override val name: String
|
||||
override val protoType: String
|
||||
}
|
||||
|
||||
|
||||
@Serializable
|
||||
data class EntityUpdate(
|
||||
override val unitNumber: UnitNumber,
|
||||
override val name: String,
|
||||
override val protoType: String,
|
||||
|
||||
override val eventCounts: Map<String, UInt>? = null,
|
||||
|
||||
val chunkPosition: MapChunkPosition? = null,
|
||||
val graphicsVariation: UByte? = null,
|
||||
val health: Float? = null,
|
||||
val isActive: Boolean? = null,
|
||||
val isRotatable: Boolean? = null,
|
||||
val lastUser: UInt? = null,
|
||||
val localisedDescription: String? = null,
|
||||
val localisedName: String? = null,
|
||||
val prototype: PrototypeName? = null,
|
||||
) : KafkatorioKeyedPacketData(), EntityUpdateKey {
|
||||
@EncodeDefault
|
||||
override val updateType: KafkatorioPacketDataType = KafkatorioPacketDataType.ENTITY
|
||||
}
|
||||
|
||||
|
||||
/* *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** */
|
||||
|
||||
|
||||
sealed interface MapChunkUpdateKey : KafkatorioKeyedPacketKey {
|
||||
val chunkPosition: MapChunkPosition
|
||||
val surfaceIndex: SurfaceIndex
|
||||
}
|
||||
|
||||
|
||||
@Serializable
|
||||
data class MapChunkUpdate(
|
||||
override val chunkPosition: MapChunkPosition,
|
||||
override val surfaceIndex: SurfaceIndex,
|
||||
|
||||
override val eventCounts: Map<String, UInt>? = null,
|
||||
|
||||
val player: PlayerIndex? = null,
|
||||
val robot: EntityIdentifiersData? = null,
|
||||
val force: ForceIndex? = null,
|
||||
/** updated tiles - might be partial and not all tiles in the chunk */
|
||||
val tileDictionary: MapTileDictionary? = null,
|
||||
val isDeleted: Boolean? = null,
|
||||
) : KafkatorioKeyedPacketData(), MapChunkUpdateKey {
|
||||
@EncodeDefault
|
||||
override val updateType: KafkatorioPacketDataType = KafkatorioPacketDataType.MAP_CHUNK
|
||||
}
|
||||
|
||||
|
||||
/* *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** */
|
||||
|
||||
|
||||
sealed interface PlayerUpdateKey : KafkatorioKeyedPacketKey {
|
||||
val index: PlayerIndex
|
||||
}
|
||||
|
||||
|
||||
@Serializable
|
||||
data class PlayerUpdate(
|
||||
override val index: PlayerIndex,
|
||||
|
||||
override val eventCounts: Map<String, UInt>? = null,
|
||||
|
||||
val characterUnitNumber: UnitNumber? = null,
|
||||
val chatColour: Colour? = null,
|
||||
val colour: Colour? = null,
|
||||
val name: String? = null,
|
||||
|
||||
val afkTime: Tick? = null,
|
||||
val ticksToRespawn: Tick? = null,
|
||||
val forceIndex: ForceIndex? = null,
|
||||
val isAdmin: Boolean? = null,
|
||||
val isConnected: Boolean? = null,
|
||||
val isShowOnMap: Boolean? = null,
|
||||
val isSpectator: Boolean? = null,
|
||||
val lastOnline: Tick? = null,
|
||||
val onlineTime: Tick? = null,
|
||||
val position: MapEntityPosition? = null,
|
||||
val surfaceIndex: SurfaceIndex? = null,
|
||||
val tag: String? = null,
|
||||
val diedCause: EntityIdentifiersData? = null,
|
||||
|
||||
val bannedReason: String? = null,
|
||||
val kickedReason: String? = null,
|
||||
val disconnectReason: String? = null,
|
||||
/** `true` when a player is removed (deleted) from the game */
|
||||
val isRemoved: Boolean? = null,
|
||||
) : KafkatorioKeyedPacketData(), PlayerUpdateKey {
|
||||
@EncodeDefault
|
||||
override val updateType: KafkatorioPacketDataType = KafkatorioPacketDataType.PLAYER
|
||||
}
|
||||
|
||||
|
||||
/* *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** */
|
||||
|
|
@ -1,13 +1,19 @@
|
|||
package dev.adamko.kafkatorio.events.schema
|
||||
package dev.adamko.kafkatorio.schema
|
||||
|
||||
import kotlin.reflect.KClass
|
||||
import kotlinx.serialization.DeserializationStrategy
|
||||
import kotlinx.serialization.KSerializer
|
||||
import kotlinx.serialization.builtins.ListSerializer
|
||||
import kotlinx.serialization.json.Json
|
||||
import kotlinx.serialization.json.JsonArray
|
||||
import kotlinx.serialization.json.JsonContentPolymorphicSerializer
|
||||
import kotlinx.serialization.json.JsonElement
|
||||
import kotlinx.serialization.json.JsonObject
|
||||
import kotlinx.serialization.json.JsonTransformingSerializer
|
||||
import kotlinx.serialization.json.buildJsonObject
|
||||
import kotlinx.serialization.json.contentOrNull
|
||||
import kotlinx.serialization.json.jsonObject
|
||||
import kotlinx.serialization.json.jsonPrimitive
|
||||
import kotlinx.serialization.modules.SerializersModule
|
||||
|
||||
|
||||
|
|
@ -44,3 +50,28 @@ class FactorioJsonListSerializer<T>(dataSerializer: KSerializer<T>) :
|
|||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
interface SerializerProvider<T : Any> {
|
||||
val serializer: KSerializer<out T>
|
||||
}
|
||||
|
||||
open class SerializerProviderDeserializer<T : Any>(
|
||||
private val baseClass: KClass<T>,
|
||||
private val key: String,
|
||||
private val selector: (String) -> SerializerProvider<out T>?,
|
||||
) : JsonContentPolymorphicSerializer<T>(baseClass) {
|
||||
|
||||
override fun selectDeserializer(element: JsonElement): DeserializationStrategy<out T> {
|
||||
|
||||
val type = element
|
||||
.jsonObject[key]
|
||||
?.jsonPrimitive
|
||||
?.contentOrNull
|
||||
?.let { json -> selector(json) }
|
||||
|
||||
requireNotNull(type) { "Unknown ${baseClass.simpleName} $key: $element" }
|
||||
|
||||
return type.serializer
|
||||
}
|
||||
}
|
||||
|
|
@ -1,9 +1,14 @@
|
|||
package dev.adamko.kafkatorio.events.schema
|
||||
package dev.adamko.kafkatorio.schema.prototypes
|
||||
|
||||
import dev.adamko.kafkatorio.events.schema.FactorioPrototype.PrototypeObjectName
|
||||
import dev.adamko.kafkatorio.schema.SerializerProvider
|
||||
import dev.adamko.kafkatorio.schema.SerializerProviderDeserializer
|
||||
import dev.adamko.kafkatorio.schema.common.Colour
|
||||
import dev.adamko.kafkatorio.schema.common.PrototypeName
|
||||
import dev.adamko.kafkatorio.schema.prototypes.FactorioPrototype.PrototypeObjectName
|
||||
import kotlinx.serialization.Contextual
|
||||
import kotlinx.serialization.DeserializationStrategy
|
||||
import kotlinx.serialization.EncodeDefault
|
||||
import kotlinx.serialization.KSerializer
|
||||
import kotlinx.serialization.Serializable
|
||||
import kotlinx.serialization.json.JsonContentPolymorphicSerializer
|
||||
import kotlinx.serialization.json.JsonElement
|
||||
|
|
@ -12,46 +17,30 @@ import kotlinx.serialization.json.jsonObject
|
|||
import kotlinx.serialization.json.jsonPrimitive
|
||||
|
||||
|
||||
|
||||
@Serializable(with = FactorioPrototypeJsonSerializer::class)
|
||||
@Serializable(with = FactorioPrototype.JsonSerializer::class)
|
||||
sealed class FactorioPrototype {
|
||||
@EncodeDefault
|
||||
abstract val prototypeObjectName: PrototypeObjectName
|
||||
|
||||
enum class PrototypeObjectName {
|
||||
LuaTilePrototype,
|
||||
enum class PrototypeObjectName(
|
||||
override val serializer: KSerializer<out FactorioPrototype>
|
||||
) : SerializerProvider<FactorioPrototype> {
|
||||
LuaTilePrototype(MapTilePrototype.serializer()),
|
||||
;
|
||||
|
||||
companion object {
|
||||
val values: List<PrototypeObjectName> = values().toList()
|
||||
val entries: Set<PrototypeObjectName> = values().toSet()
|
||||
}
|
||||
}
|
||||
|
||||
object JsonSerializer : SerializerProviderDeserializer<FactorioPrototype>(
|
||||
FactorioPrototype::class,
|
||||
FactorioPrototype::prototypeObjectName.name,
|
||||
{ json -> PrototypeObjectName.entries.firstOrNull { it.name == json } }
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
object FactorioPrototypeJsonSerializer : JsonContentPolymorphicSerializer<FactorioPrototype>(
|
||||
FactorioPrototype::class
|
||||
) {
|
||||
private val key = FactorioPrototype::prototypeObjectName.name
|
||||
|
||||
override fun selectDeserializer(element: JsonElement): DeserializationStrategy<out FactorioPrototype> {
|
||||
|
||||
val type = element
|
||||
.jsonObject[key]
|
||||
?.jsonPrimitive
|
||||
?.contentOrNull
|
||||
?.let { json ->
|
||||
PrototypeObjectName.values.firstOrNull { it.name == json }
|
||||
}
|
||||
|
||||
requireNotNull(type) { "Unknown FactorioPrototype ${key}: $element" }
|
||||
|
||||
return when (type) {
|
||||
PrototypeObjectName.LuaTilePrototype -> MapTilePrototype.serializer()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Serializable
|
||||
data class MapTilePrototype(
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
package dev.adamko.kafkatorio.schema.prototypes
|
||||
|
||||
import dev.adamko.kafkatorio.schema.common.Colour
|
||||
import dev.adamko.kafkatorio.schema.common.PrototypeName
|
||||
import kotlinx.serialization.Contextual
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
|
||||
@Serializable
|
||||
sealed class FactorioPrototype2 {
|
||||
|
||||
@Serializable
|
||||
data class MapTile(
|
||||
val name: PrototypeName,
|
||||
val layer: UInt,
|
||||
val mapColour: Colour,
|
||||
@Contextual
|
||||
val collisionMasks: List<String>,
|
||||
val order: String,
|
||||
/** Can the tile be mined for resources? */
|
||||
val canBeMined: Boolean,
|
||||
) : FactorioPrototype2()
|
||||
}
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
package dev.adamko.kafkatorio.schema2
|
||||
|
||||
import dev.adamko.kafkatorio.schema.common.Tick
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
|
||||
@Serializable
|
||||
data class KafkatorioPacket2(
|
||||
val modVersion: String,
|
||||
val tick: Tick,
|
||||
val data: KafkatorioPacketData2,
|
||||
)
|
||||
|
||||
|
||||
@Serializable
|
||||
sealed class KafkatorioPacketData2
|
||||
|
|
@ -0,0 +1,70 @@
|
|||
package dev.adamko.kafkatorio.schema2
|
||||
|
||||
import dev.adamko.kafkatorio.schema.common.PlayerIndex
|
||||
import dev.adamko.kafkatorio.schema.common.SurfaceIndex
|
||||
import dev.adamko.kafkatorio.schema.prototypes.FactorioPrototype
|
||||
import dev.adamko.kafkatorio.schema.prototypes.FactorioPrototype2
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
|
||||
/** No key, no caching. Must be sent instantly. */
|
||||
@Serializable
|
||||
sealed class KafkatorioInstantPacketData2 : KafkatorioPacketData2()
|
||||
|
||||
|
||||
/* *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** */
|
||||
|
||||
|
||||
@Serializable
|
||||
data class ConfigurationUpdate(
|
||||
val factorioData: ConfigurationUpdateGameData,
|
||||
val allMods: List<ConfigurationUpdateModData>,
|
||||
val modStartupSettingsChange: Boolean,
|
||||
val migrationApplied: Boolean,
|
||||
) : KafkatorioInstantPacketData2() {
|
||||
|
||||
@Serializable
|
||||
data class ConfigurationUpdateGameData(
|
||||
val oldVersion: String? = null,
|
||||
val newVersion: String? = null,
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class ConfigurationUpdateModData(
|
||||
val modName: String,
|
||||
val currentVersion: String? = null,
|
||||
val previousVersion: String? = null,
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@Serializable
|
||||
data class ConsoleChatUpdate(
|
||||
val authorPlayerIndex: PlayerIndex?,
|
||||
val content: String,
|
||||
) : KafkatorioInstantPacketData2()
|
||||
|
||||
|
||||
@Serializable
|
||||
data class ConsoleCommandUpdate(
|
||||
val authorPlayerIndex: PlayerIndex?,
|
||||
val command: String,
|
||||
val parameters: String,
|
||||
) : KafkatorioInstantPacketData2()
|
||||
|
||||
|
||||
@Serializable
|
||||
data class PrototypesUpdate(
|
||||
val prototypes: List<FactorioPrototype2>,
|
||||
) : KafkatorioInstantPacketData2()
|
||||
|
||||
|
||||
@Serializable
|
||||
data class SurfaceUpdate(
|
||||
val index: SurfaceIndex,
|
||||
val daytime: Double,
|
||||
val name: String,
|
||||
) : KafkatorioInstantPacketData2()
|
||||
|
||||
|
||||
/* *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** */
|
||||
|
|
@ -0,0 +1,126 @@
|
|||
package dev.adamko.kafkatorio.schema2
|
||||
|
||||
import dev.adamko.kafkatorio.schema.common.Colour
|
||||
import dev.adamko.kafkatorio.schema.common.EntityIdentifiers
|
||||
import dev.adamko.kafkatorio.schema.common.EntityIdentifiersData
|
||||
import dev.adamko.kafkatorio.schema.common.ForceIndex
|
||||
import dev.adamko.kafkatorio.schema.common.MapChunkPosition
|
||||
import dev.adamko.kafkatorio.schema.common.MapEntityPosition
|
||||
import dev.adamko.kafkatorio.schema.common.MapTileDictionary
|
||||
import dev.adamko.kafkatorio.schema.common.PlayerIndex
|
||||
import dev.adamko.kafkatorio.schema.common.PrototypeName
|
||||
import dev.adamko.kafkatorio.schema.common.SurfaceIndex
|
||||
import dev.adamko.kafkatorio.schema.common.Tick
|
||||
import dev.adamko.kafkatorio.schema.common.UnitNumber
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
|
||||
/** Has a key, so it can be debounced */
|
||||
@Serializable
|
||||
sealed class KafkatorioKeyedPacketData2 : KafkatorioPacketData2() {
|
||||
/** Count how many events this packet is aggregating data from */
|
||||
abstract val key: KafkatorioKeyedPacketKey2
|
||||
abstract val eventCounts: Map<String, UInt>?
|
||||
}
|
||||
|
||||
@Serializable
|
||||
sealed class KafkatorioKeyedPacketKey2
|
||||
|
||||
|
||||
/* *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** */
|
||||
|
||||
|
||||
@Serializable
|
||||
data class EntityUpdateKey(
|
||||
/** While normally optional, here a [unitNumber] is required for caching */
|
||||
override val unitNumber: UnitNumber,
|
||||
override val name: String,
|
||||
override val protoType: String,
|
||||
) : EntityIdentifiers, KafkatorioKeyedPacketKey2()
|
||||
|
||||
|
||||
@Serializable
|
||||
data class EntityUpdate(
|
||||
override val key: EntityUpdateKey,
|
||||
|
||||
override val eventCounts: Map<String, UInt>? = null,
|
||||
|
||||
val chunkPosition: MapChunkPosition? = null,
|
||||
val graphicsVariation: UByte? = null,
|
||||
val health: Float? = null,
|
||||
val isActive: Boolean? = null,
|
||||
val isRotatable: Boolean? = null,
|
||||
val lastUser: UInt? = null,
|
||||
val localisedDescription: String? = null,
|
||||
val localisedName: String? = null,
|
||||
val prototype: PrototypeName? = null,
|
||||
) : KafkatorioKeyedPacketData2()
|
||||
|
||||
|
||||
/* *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** */
|
||||
|
||||
|
||||
@Serializable
|
||||
data class MapChunkUpdateKey(
|
||||
val chunkPosition: MapChunkPosition,
|
||||
val surfaceIndex: SurfaceIndex,
|
||||
) : KafkatorioKeyedPacketKey2()
|
||||
|
||||
|
||||
@Serializable
|
||||
data class MapChunkUpdate(
|
||||
override val key: MapChunkUpdateKey,
|
||||
|
||||
override val eventCounts: Map<String, UInt>? = null,
|
||||
|
||||
val player: PlayerIndex? = null,
|
||||
val robot: EntityIdentifiersData? = null,
|
||||
val force: ForceIndex? = null,
|
||||
/** updated tiles - might be partial and not all tiles in the chunk */
|
||||
val tileDictionary: MapTileDictionary? = null,
|
||||
val isDeleted: Boolean? = null,
|
||||
) : KafkatorioKeyedPacketData2()
|
||||
|
||||
|
||||
/* *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** */
|
||||
|
||||
@Serializable
|
||||
data class PlayerUpdateKey(
|
||||
val index: PlayerIndex,
|
||||
) : KafkatorioKeyedPacketKey2()
|
||||
|
||||
|
||||
@Serializable
|
||||
data class PlayerUpdate(
|
||||
override val key: PlayerUpdateKey,
|
||||
|
||||
override val eventCounts: Map<String, UInt>? = null,
|
||||
|
||||
val characterUnitNumber: UnitNumber? = null,
|
||||
val chatColour: Colour? = null,
|
||||
val colour: Colour? = null,
|
||||
val name: String? = null,
|
||||
|
||||
val afkTime: Tick? = null,
|
||||
val ticksToRespawn: Tick? = null,
|
||||
val forceIndex: ForceIndex? = null,
|
||||
val isAdmin: Boolean? = null,
|
||||
val isConnected: Boolean? = null,
|
||||
val isShowOnMap: Boolean? = null,
|
||||
val isSpectator: Boolean? = null,
|
||||
val lastOnline: Tick? = null,
|
||||
val onlineTime: Tick? = null,
|
||||
val position: MapEntityPosition? = null,
|
||||
val surfaceIndex: SurfaceIndex? = null,
|
||||
val tag: String? = null,
|
||||
val diedCause: EntityIdentifiersData? = null,
|
||||
|
||||
val bannedReason: String? = null,
|
||||
val kickedReason: String? = null,
|
||||
val disconnectReason: String? = null,
|
||||
/** `true` when a player is removed (deleted) from the game */
|
||||
val isRemoved: Boolean? = null,
|
||||
) : KafkatorioKeyedPacketData2()
|
||||
|
||||
|
||||
/* *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** */
|
||||
|
|
@ -1,7 +1,10 @@
|
|||
package dev.adamko.kafkatorio.events.schema
|
||||
|
||||
import dev.adamko.kafkatorio.events.schema.converters.toHex
|
||||
import dev.adamko.kafkatorio.events.schema.converters.toPercentile
|
||||
import dev.adamko.kafkatorio.schema.common.Colour
|
||||
import dev.adamko.kafkatorio.schema.common.ColourHex
|
||||
import dev.adamko.kafkatorio.schema.common.toHex
|
||||
import dev.adamko.kafkatorio.schema.common.toPercentile
|
||||
import dev.adamko.kafkatorio.schema.jsonMapperKafkatorio
|
||||
import io.kotest.assertions.withClue
|
||||
import io.kotest.core.spec.style.BehaviorSpec
|
||||
import io.kotest.matchers.floats.shouldBeWithinPercentageOf
|
||||
|
|
@ -81,6 +84,4 @@ class ColourHexTest : BehaviorSpec({
|
|||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
})
|
||||
|
|
|
|||
|
|
@ -1,342 +0,0 @@
|
|||
package dev.adamko.kafkatorio.events.schema
|
||||
|
||||
import io.kotest.assertions.json.shouldEqualJson
|
||||
import io.kotest.core.spec.style.FunSpec
|
||||
import io.kotest.matchers.shouldBe
|
||||
import kotlinx.serialization.decodeFromString
|
||||
import kotlinx.serialization.encodeToString
|
||||
|
||||
class FactorioEventTest : FunSpec({
|
||||
|
||||
// context("Given: json on_player_joined_game") {
|
||||
//
|
||||
// // language=JSON
|
||||
// val json = """
|
||||
// {
|
||||
// "data": {
|
||||
// "objectName": "LuaPlayer",
|
||||
// "force": 1,
|
||||
// "surface": 1,
|
||||
// "name": "fredthedeadhead",
|
||||
// "characterUnitNumber": 1,
|
||||
// "associatedCharactersUnitNumbers": {},
|
||||
// "position": {
|
||||
// "x": 30.6,
|
||||
// "y": 84.1
|
||||
// },
|
||||
// "colour": {
|
||||
// "red": 0.86,
|
||||
// "green": 0.5,
|
||||
// "blue": 0.12,
|
||||
// "alpha": 0.5
|
||||
// },
|
||||
// "chatColour": {
|
||||
// "red": 1,
|
||||
// "green": 0.62,
|
||||
// "blue": 0.25
|
||||
// },
|
||||
// "lastOnline": 2287061
|
||||
// },
|
||||
// "packetType": "EVENT",
|
||||
// "eventType": "on_player_joined_game",
|
||||
// "modVersion": "0.2.1",
|
||||
// "tick": 2287072
|
||||
// }
|
||||
// """.trimIndent()
|
||||
// context("When: decoded") {
|
||||
// val actual: KafkatorioPacket = jsonMapperKafkatorio.decodeFromString(json)
|
||||
//
|
||||
// test("Then: parse") {
|
||||
//
|
||||
// val expected = FactorioEvent(
|
||||
// data = PlayerData(
|
||||
// name = "fredthedeadhead",
|
||||
// surface = SurfaceIndex(1u),
|
||||
// force = ForceIndex(1u),
|
||||
// characterUnitNumber = UnitNumber(1u),
|
||||
// associatedCharactersUnitNumbers = emptyList(),
|
||||
// position = MapEntityPosition(
|
||||
// x = 30.6,
|
||||
// y = 84.1,
|
||||
// ),
|
||||
// colour = Colour(
|
||||
// red = 0.86f,
|
||||
// green = 0.5f,
|
||||
// blue = 0.12f,
|
||||
// alpha = 0.5f
|
||||
// ),
|
||||
// chatColour = Colour(
|
||||
// red = 1f,
|
||||
// green = 0.62f,
|
||||
// blue = 0.25f,
|
||||
// alpha = 1f
|
||||
// ),
|
||||
// lastOnline = Tick(2287061u)
|
||||
// ),
|
||||
// eventType = "on_player_joined_game",
|
||||
// modVersion = "0.2.1",
|
||||
// tick = Tick(2287072u)
|
||||
// )
|
||||
//
|
||||
// actual shouldBe expected
|
||||
//
|
||||
// expected.data.objectName shouldBe FactorioObjectData.ObjectName.LuaPlayer
|
||||
// }
|
||||
//
|
||||
// test("Then: expect encode equals json") {
|
||||
// val encoded = jsonMapperKafkatorio.encodeToString(actual)
|
||||
// encoded.shouldEqualJson(json)
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// context("Given: json LuaSurface") {
|
||||
// // language=JSON
|
||||
// val json = """
|
||||
// {
|
||||
// "data": {
|
||||
// "objectName": "LuaSurface",
|
||||
// "name": "nauvis",
|
||||
// "index": 1,
|
||||
// "daytime": 0.745
|
||||
// },
|
||||
// "packetType": "EVENT",
|
||||
// "eventType": "on_tick",
|
||||
// "modVersion": "0.2.1",
|
||||
// "tick": 2301240
|
||||
// }
|
||||
// """.trimIndent()
|
||||
//
|
||||
// context("When: decoded") {
|
||||
// val actual: KafkatorioPacket = jsonMapperKafkatorio.decodeFromString(json)
|
||||
//
|
||||
// test("should equal obj") {
|
||||
//
|
||||
// val expected = FactorioEvent(
|
||||
// data = SurfaceData(
|
||||
// name = "nauvis",
|
||||
// index = SurfaceIndex(1u),
|
||||
// daytime = 0.745
|
||||
// ),
|
||||
// eventType = "on_tick",
|
||||
// modVersion = "0.2.1",
|
||||
// tick = Tick(2301240u)
|
||||
// )
|
||||
//
|
||||
// actual shouldBe expected
|
||||
//
|
||||
// expected.data.objectName shouldBe FactorioObjectData.ObjectName.LuaSurface
|
||||
// }
|
||||
//
|
||||
// test("Then: expect encode equals json") {
|
||||
// val encoded = jsonMapperKafkatorio.encodeToString(actual)
|
||||
// encoded.shouldEqualJson(json)
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// context("Given: LuaEntity on_player_changed_position") {
|
||||
// // language=JSON
|
||||
// val json = """
|
||||
// {
|
||||
// "data": {
|
||||
// "objectName": "LuaEntity",
|
||||
// "name": "character",
|
||||
// "type": "character",
|
||||
// "active": true,
|
||||
// "health": 250,
|
||||
// "healthRatio": 1,
|
||||
// "surface": 1,
|
||||
// "force": 1,
|
||||
// "unitNumber": 1,
|
||||
// "position": {
|
||||
// "x": 37.5,
|
||||
// "y": 81.1
|
||||
// }
|
||||
// },
|
||||
// "packetType": "EVENT",
|
||||
// "eventType": "on_player_changed_position",
|
||||
// "modVersion": "0.2.1",
|
||||
// "tick": 2301231
|
||||
// }
|
||||
// """.trimIndent()
|
||||
// context("When: decoded") {
|
||||
// val actual: KafkatorioPacket = jsonMapperKafkatorio.decodeFromString(json)
|
||||
//
|
||||
// test("parse") {
|
||||
//
|
||||
// val expected = FactorioEvent(
|
||||
// data = EntityData(
|
||||
// name = "character",
|
||||
// type = "character",
|
||||
// active = true,
|
||||
// health = 250.0,
|
||||
// healthRatio = 1.0,
|
||||
// surface = SurfaceIndex(1u),
|
||||
// unitNumber = UnitNumber(1u),
|
||||
// force = ForceIndex(1u),
|
||||
// playerIndex = null,
|
||||
// position = MapEntityPosition(
|
||||
// x = 37.5,
|
||||
// y = 81.1,
|
||||
// )
|
||||
// ),
|
||||
// eventType = "on_player_changed_position",
|
||||
// modVersion = "0.2.1",
|
||||
// tick = Tick(2301231u)
|
||||
// )
|
||||
//
|
||||
// actual shouldBe expected
|
||||
//
|
||||
// expected.data.objectName shouldBe FactorioObjectData.ObjectName.LuaEntity
|
||||
// }
|
||||
//
|
||||
// test("Then: expect encode equals json") {
|
||||
// val encoded = jsonMapperKafkatorio.encodeToString(actual)
|
||||
// encoded.shouldEqualJson(json)
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// }
|
||||
//
|
||||
// context("Given: LuaTiles on_player_built_tile") {
|
||||
// // language=JSON
|
||||
// val json = """
|
||||
// {
|
||||
// "data": {
|
||||
// "objectName": "LuaTiles",
|
||||
// "surfaceIndex": 1,
|
||||
// "tiles": [
|
||||
// {
|
||||
// "y": 10,
|
||||
// "x": -65,
|
||||
// "proto": "refined-concrete"
|
||||
// },
|
||||
// {
|
||||
// "y": 11,
|
||||
// "x": -65,
|
||||
// "proto": "red-earth"
|
||||
// },
|
||||
// {
|
||||
// "y": 10,
|
||||
// "x": -64,
|
||||
// "proto": "blue-cheese"
|
||||
// },
|
||||
// {
|
||||
// "y": 11,
|
||||
// "x": -64,
|
||||
// "proto": "refined-concrete"
|
||||
// }
|
||||
// ]
|
||||
// },
|
||||
// "packetType": "EVENT",
|
||||
// "eventType": "on_player_built_tile",
|
||||
// "modVersion": "0.2.2",
|
||||
// "tick": 2429929
|
||||
// }
|
||||
// """.trimIndent()
|
||||
// context("When: decoded") {
|
||||
// val actual: KafkatorioPacket = jsonMapperKafkatorio.decodeFromString(json)
|
||||
//
|
||||
// test("parse") {
|
||||
//
|
||||
// val expected = FactorioEvent(
|
||||
// data = MapTiles(
|
||||
// surfaceIndex = SurfaceIndex(1u),
|
||||
// tiles = listOf(
|
||||
// MapTile(
|
||||
// proto = PrototypeName("refined-concrete"),
|
||||
// x = -65,
|
||||
// y = 10,
|
||||
// ),
|
||||
// MapTile(
|
||||
// proto = PrototypeName("red-earth"),
|
||||
// x = -65,
|
||||
// y = 11,
|
||||
// ),
|
||||
// MapTile(
|
||||
// proto = PrototypeName("blue-cheese"),
|
||||
// x = -64,
|
||||
// y = 10,
|
||||
// ),
|
||||
// MapTile(
|
||||
// proto = PrototypeName("refined-concrete"),
|
||||
// x = -64,
|
||||
// y = 11,
|
||||
// ),
|
||||
// )
|
||||
// ),
|
||||
// eventType = "on_player_built_tile",
|
||||
// modVersion = "0.2.2",
|
||||
// tick = Tick(2429929u)
|
||||
// )
|
||||
//
|
||||
// actual shouldBe expected
|
||||
//
|
||||
// expected.data.objectName shouldBe FactorioObjectData.ObjectName.LuaTiles
|
||||
// }
|
||||
//
|
||||
// test("Then: expect encode equals json") {
|
||||
// val encoded = jsonMapperKafkatorio.encodeToString(actual)
|
||||
// encoded.shouldEqualJson(json)
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// context("Given: packet FactorioConfigurationUpdate") {
|
||||
// // language=JSON
|
||||
// val json = """
|
||||
// {
|
||||
// "modVersion": "0.2.8",
|
||||
// "packetType": "CONFIG",
|
||||
// "tick": 534,
|
||||
// "allMods": [
|
||||
// {
|
||||
// "modName": "kafkatorio-events",
|
||||
// "currentVersion": "0.2.8",
|
||||
// "previousVersion": "0.2.7"
|
||||
// },
|
||||
// {
|
||||
// "modName": "base",
|
||||
// "currentVersion": "1.1.53"
|
||||
// }
|
||||
// ],
|
||||
// "factorioData": {}
|
||||
// }
|
||||
// """.trimIndent()
|
||||
// context("When: decoded") {
|
||||
// val actual: FactorioConfigurationUpdate = jsonMapperKafkatorio.decodeFromString(json)
|
||||
//
|
||||
// test("parse") {
|
||||
//
|
||||
// val expected = FactorioConfigurationUpdate(
|
||||
// modVersion = "0.2.8",
|
||||
// tick = Tick(534u),
|
||||
// factorioData = FactorioGameDataUpdate(null, null),
|
||||
// allMods = listOf(
|
||||
// FactorioModInfo(
|
||||
// modName = "kafkatorio-events",
|
||||
// currentVersion = "0.2.8",
|
||||
// previousVersion = "0.2.7"
|
||||
// ),
|
||||
// FactorioModInfo(
|
||||
// modName = "base",
|
||||
// currentVersion = "1.1.53",
|
||||
// previousVersion = null
|
||||
// ),
|
||||
// )
|
||||
// )
|
||||
//
|
||||
// actual shouldBe expected
|
||||
//
|
||||
// expected.packetType shouldBe KafkatorioPacket.PacketType.CONFIG
|
||||
// }
|
||||
//
|
||||
// test("Then: expect encode equals json") {
|
||||
// val encoded = jsonMapperKafkatorio.encodeToString(actual)
|
||||
// encoded.shouldEqualJson(json)
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// }
|
||||
})
|
||||
|
|
@ -0,0 +1,93 @@
|
|||
package dev.adamko.kafkatorio.events.schema
|
||||
|
||||
import dev.adamko.kafkatorio.schema.common.MapChunkPosition
|
||||
import dev.adamko.kafkatorio.schema.common.MapTileDictionary
|
||||
import dev.adamko.kafkatorio.schema.common.PrototypeName
|
||||
import dev.adamko.kafkatorio.schema.common.SurfaceIndex
|
||||
import dev.adamko.kafkatorio.schema.common.Tick
|
||||
import dev.adamko.kafkatorio.schema.jsonMapperKafkatorio
|
||||
import dev.adamko.kafkatorio.schema2.KafkatorioPacket2
|
||||
import dev.adamko.kafkatorio.schema2.MapChunkUpdate
|
||||
import dev.adamko.kafkatorio.schema2.MapChunkUpdateKey
|
||||
import io.kotest.core.spec.style.FunSpec
|
||||
import io.kotest.matchers.shouldBe
|
||||
import kotlin.math.exp
|
||||
import kotlinx.serialization.decodeFromString
|
||||
import kotlinx.serialization.encodeToString
|
||||
|
||||
class MapChunkUpdateTest : FunSpec({
|
||||
|
||||
context("MapChunkUpdate update") {
|
||||
//language=json
|
||||
val json = """
|
||||
{
|
||||
"data": {
|
||||
"type": "dev.adamko.kafkatorio.schema2.MapChunkUpdate",
|
||||
"key": {
|
||||
"surfaceIndex": 1,
|
||||
"chunkPosition": [
|
||||
-6,
|
||||
-3
|
||||
]
|
||||
},
|
||||
"tileDictionary": {
|
||||
"tilesXY": {
|
||||
"-192": {
|
||||
"-65": 2
|
||||
},
|
||||
"161": {
|
||||
"-96": 1,
|
||||
"-95": 2,
|
||||
"94": 2,
|
||||
"93": 32
|
||||
}
|
||||
},
|
||||
"protos": {
|
||||
"water": 1,
|
||||
"grass-4": 2,
|
||||
"grass-3": 32
|
||||
}
|
||||
},
|
||||
"eventCounts": {
|
||||
"on_chunk_generated": 1
|
||||
}
|
||||
},
|
||||
"modVersion": "0.4.0",
|
||||
"tick": 36
|
||||
}
|
||||
""".trimIndent()
|
||||
|
||||
val expected = KafkatorioPacket2(
|
||||
tick = Tick(36u),
|
||||
modVersion = "0.4.0",
|
||||
data = MapChunkUpdate(
|
||||
key = MapChunkUpdateKey(
|
||||
MapChunkPosition(-6, -3),
|
||||
SurfaceIndex(1u),
|
||||
),
|
||||
eventCounts = mapOf("on_chunk_generated" to 1u),
|
||||
tileDictionary = MapTileDictionary(
|
||||
tilesXY = mapOf(
|
||||
"-192" to mapOf("-65" to MapTileDictionary.PrototypeKey(2)),
|
||||
"161" to mapOf(
|
||||
"-96" to MapTileDictionary.PrototypeKey(1),
|
||||
"-95" to MapTileDictionary.PrototypeKey(2),
|
||||
"94" to MapTileDictionary.PrototypeKey(2),
|
||||
"93" to MapTileDictionary.PrototypeKey(32),
|
||||
),
|
||||
),
|
||||
protos = mapOf(
|
||||
PrototypeName("water") to MapTileDictionary.PrototypeKey(1),
|
||||
PrototypeName("grass-4") to MapTileDictionary.PrototypeKey(2),
|
||||
PrototypeName("grass-3") to MapTileDictionary.PrototypeKey(32),
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
test("decode") {
|
||||
val packet: KafkatorioPacket2 = jsonMapperKafkatorio.decodeFromString(json)
|
||||
packet shouldBe expected
|
||||
}
|
||||
}
|
||||
})
|
||||
|
|
@ -0,0 +1,42 @@
|
|||
package dev.adamko.kafkatorio.schema.common
|
||||
|
||||
import io.kotest.core.spec.style.FunSpec
|
||||
import io.kotest.matchers.collections.shouldContainAll
|
||||
|
||||
class MapTileDictionaryTest : FunSpec({
|
||||
|
||||
context("MapTileDictionary") {
|
||||
val actual = MapTileDictionary(
|
||||
tilesXY = mapOf(
|
||||
"-192" to mapOf("-65" to MapTileDictionary.PrototypeKey(2)),
|
||||
"161" to mapOf(
|
||||
"-96" to MapTileDictionary.PrototypeKey(1),
|
||||
"-95" to MapTileDictionary.PrototypeKey(2),
|
||||
"94" to MapTileDictionary.PrototypeKey(2),
|
||||
"93" to MapTileDictionary.PrototypeKey(32),
|
||||
),
|
||||
|
||||
// unknown prototype
|
||||
"44" to mapOf("1" to MapTileDictionary.PrototypeKey(44)),
|
||||
),
|
||||
protos = mapOf(
|
||||
PrototypeName("water") to MapTileDictionary.PrototypeKey(1),
|
||||
PrototypeName("grass-4") to MapTileDictionary.PrototypeKey(2),
|
||||
PrototypeName("grass-3") to MapTileDictionary.PrototypeKey(32),
|
||||
|
||||
PrototypeName("not-used") to MapTileDictionary.PrototypeKey(99),
|
||||
)
|
||||
)
|
||||
|
||||
test("toMapTileList") {
|
||||
actual.toMapTileList() shouldContainAll listOf(
|
||||
MapTile(-192, -65, PrototypeName("grass-4")),
|
||||
|
||||
MapTile(161, -96, PrototypeName("water")),
|
||||
MapTile(161, -95, PrototypeName("grass-4")),
|
||||
MapTile(161, 94, PrototypeName("grass-4")),
|
||||
MapTile(161, 93, PrototypeName("grass-3")),
|
||||
)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
|
@ -1,5 +1,10 @@
|
|||
package dev.adamko.kafkatorio.events.schema
|
||||
|
||||
import dev.adamko.kafkatorio.schema.events.KafkatorioInstantPacketData
|
||||
import dev.adamko.kafkatorio.schema.events.KafkatorioKeyedPacketData
|
||||
import dev.adamko.kafkatorio.schema.events.KafkatorioKeyedPacketKey
|
||||
//import dev.adamko.kafkatorio.schema.events.KafkatorioPacket
|
||||
import dev.adamko.kafkatorio.schema.prototypes.FactorioPrototype
|
||||
import kotlin.io.path.Path
|
||||
import kotlin.io.path.createDirectories
|
||||
import me.ntrrgc.tsGenerator.TypeScriptGenerator
|
||||
|
|
@ -10,40 +15,38 @@ import org.intellij.lang.annotations.Language
|
|||
*/
|
||||
fun main(args: Array<String>) {
|
||||
|
||||
val gen =
|
||||
TypeScriptGenerator(
|
||||
rootClasses = buildSet {
|
||||
addAll(KafkatorioPacket::class.sealedSubclasses)
|
||||
val gen = TypeScriptGenerator(
|
||||
rootClasses = buildSet {
|
||||
// add(KafkatorioPacket::class)
|
||||
// addAll(KafkatorioPacket::class.sealedSubclasses)
|
||||
|
||||
addAll(FactorioObjectData::class.sealedSubclasses)
|
||||
add(KafkatorioInstantPacketData::class)
|
||||
addAll(KafkatorioInstantPacketData::class.sealedSubclasses)
|
||||
|
||||
addAll(FactorioPrototype::class.sealedSubclasses)
|
||||
add(FactorioPrototypes::class)
|
||||
add(FactorioConfigurationUpdate::class)
|
||||
add(KafkatorioKeyedPacketData::class)
|
||||
addAll(KafkatorioKeyedPacketData::class.sealedSubclasses)
|
||||
addAll(KafkatorioKeyedPacketKey::class.sealedSubclasses)
|
||||
|
||||
add(FactorioEvent::class)
|
||||
|
||||
add(FactorioEventUpdatePacket::class)
|
||||
addAll(FactorioEventUpdate::class.sealedSubclasses)
|
||||
addAll(FactorioEventUpdateKey::class.sealedSubclasses)
|
||||
},
|
||||
mappings = mapOf(
|
||||
// builtin Factorio numeric types > `typed-factorio/generated/builtin-types.d.ts`
|
||||
Float::class to "float",
|
||||
Double::class to "double",
|
||||
Int::class to "int",
|
||||
Byte::class to "int8",
|
||||
UInt::class to "uint",
|
||||
UByte::class to "uint8",
|
||||
UShort::class to "uint16",
|
||||
ULong::class to "uint64",
|
||||
),
|
||||
classTransformers = listOf(
|
||||
ValueClassTransformer,
|
||||
add(FactorioPrototype::class)
|
||||
addAll(FactorioPrototype::class.sealedSubclasses)
|
||||
},
|
||||
mappings = mapOf(
|
||||
// builtin Factorio numeric types > `typed-factorio/generated/builtin-types.d.ts`
|
||||
Float::class to "float",
|
||||
Double::class to "double",
|
||||
Int::class to "int",
|
||||
Byte::class to "int8",
|
||||
UInt::class to "uint",
|
||||
UByte::class to "uint8",
|
||||
UShort::class to "uint16",
|
||||
ULong::class to "uint64",
|
||||
),
|
||||
classTransformers = listOf(
|
||||
ValueClassTransformer,
|
||||
// PropertyNameTransformer,
|
||||
),
|
||||
),
|
||||
// voidType = VoidType.UNDEFINED,
|
||||
)
|
||||
)
|
||||
|
||||
val definitions = gen.definitionsText
|
||||
|
||||
|
|
@ -57,7 +60,7 @@ fun main(args: Array<String>) {
|
|||
.forEach { (defFile, def) ->
|
||||
val outFile = outPath.resolve(defFile).toFile()
|
||||
outFile.printWriter().use {
|
||||
it.println(header)
|
||||
it.println(kt2tsHeader)
|
||||
it.print(def)
|
||||
}
|
||||
println("Generated file: $outFile")
|
||||
|
|
@ -67,6 +70,7 @@ fun main(args: Array<String>) {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
/** Split the TypeScript interfaces into different files */
|
||||
private fun splitDefinitions(definitions: String): Map<String, String> {
|
||||
return definitions
|
||||
|
|
@ -74,51 +78,64 @@ private fun splitDefinitions(definitions: String): Map<String, String> {
|
|||
.groupBy { def ->
|
||||
|
||||
when {
|
||||
def isInterface "KafkatorioPacket"
|
||||
|| def extendsAny listOf("KafkatorioPacket")
|
||||
|| def isType "KafkatorioPacketDataType"
|
||||
-> "kafkatorio-packet.d.ts"
|
||||
|
||||
"interface FactorioObjectData " in def ||
|
||||
"type ObjectName " in def ||
|
||||
def.contains(Regex("interface.+extends.+(FactorioObjectData).*"))
|
||||
-> "object-data.d.ts"
|
||||
def isInterface "KafkatorioInstantPacketData"
|
||||
|| def extends "KafkatorioInstantPacketData"
|
||||
|| def isType "KafkatorioInstantPacketDataType"
|
||||
|
||||
"extends FactorioPrototype " in def ||
|
||||
"interface FactorioPrototypes " in def ||
|
||||
"interface FactorioPrototype " in def ||
|
||||
"type PrototypeObjectName " in def -> "prototype.d.ts"
|
||||
|| def isInterface "ConfigurationUpdateGameData"
|
||||
|| def isInterface "ConfigurationUpdateModData"
|
||||
-> "kafkatorio-instant-packet.d.ts"
|
||||
|
||||
"extends FactorioConfigurationUpdate " in def ||
|
||||
"interface FactorioConfigurationUpdate " in def ||
|
||||
"FactorioGameDataUpdate " in def ||
|
||||
"FactorioModInfo " in def -> "config-update.d.ts"
|
||||
def isInterface "KafkatorioKeyedPacket"
|
||||
|| def isInterface "KafkatorioKeyedPacketData"
|
||||
|| def extends "KafkatorioKeyedPacket"
|
||||
|| def isType "KafkatorioKeyedPacketDataType"
|
||||
|
||||
"interface FactorioEventUpdatePacket" in def ||
|
||||
"type FactorioEventUpdateType =" in def ||
|
||||
"interface FactorioEventUpdateKey {" in def ||
|
||||
"interface FactorioEventUpdateData {" in def ||
|
||||
def.contains(
|
||||
Regex(
|
||||
"""
|
||||
interface.+extends.+(FactorioEventUpdate|PlayerUpdateKey|PlayerUpdateData).*
|
||||
""".trimIndent()
|
||||
)
|
||||
)
|
||||
-> "update.d.ts"
|
||||
|| def isInterface "KafkatorioKeyedPacketKey"
|
||||
-> "kafkatorio-keyed-packet.d.ts"
|
||||
|
||||
else -> "schema.d.ts"
|
||||
def isInterface "FactorioPrototypes"
|
||||
|| def isInterface "FactorioPrototype"
|
||||
|| def extends "FactorioPrototype"
|
||||
|| def isType "PrototypeObjectName"
|
||||
-> "prototype.d.ts"
|
||||
|
||||
else -> "schema.d.ts"
|
||||
}
|
||||
}
|
||||
.mapValues { (_, def) ->
|
||||
def
|
||||
.sorted()
|
||||
.joinToString("\n\n", postfix = "\n")
|
||||
.replace(" ", " ")
|
||||
}
|
||||
}
|
||||
|
||||
@Language("TypeScript")
|
||||
val header = """
|
||||
val kt2tsHeader = """
|
||||
|// Generated by TypeScriptGenerator - do not edit this file manually
|
||||
|
|
||||
""".trimMargin()
|
||||
|
||||
private infix fun String.extends(types: String): Boolean =
|
||||
contains(Regex(".+extends.*(${types})"))
|
||||
|
||||
private infix fun String.extendsAny(types: List<String>): Boolean =
|
||||
extends(types.joinToString("|") { "$it,".replaceAfterLast(',', "") })
|
||||
|
||||
private infix fun String.isInterface(type: String): Boolean =
|
||||
contains(Regex("interface $type.*"))
|
||||
|
||||
|
||||
private infix fun String.isType(type: String): Boolean =
|
||||
contains(Regex("type $type.*"))
|
||||
|
||||
|
||||
//object PropertyNameTransformer : ClassTransformer {
|
||||
// override fun transformPropertyName(
|
||||
// propertyName: String,
|
||||
|
|
|
|||
|
|
@ -0,0 +1,55 @@
|
|||
package dev.adamko.kafkatorio.events.schema
|
||||
|
||||
import dev.adamko.kafkatorio.schema2.KafkatorioPacket2
|
||||
import dev.adamko.kxstsgen.KxsTsConfig
|
||||
import dev.adamko.kxstsgen.KxsTsGenerator
|
||||
import dev.adamko.kxstsgen.core.TsDeclaration.TsTypeAlias
|
||||
import dev.adamko.kxstsgen.core.TsElementId
|
||||
import dev.adamko.kxstsgen.core.TsLiteral
|
||||
import dev.adamko.kxstsgen.core.TsTypeRef
|
||||
import kotlin.io.path.Path
|
||||
import kotlin.io.path.createDirectories
|
||||
import kotlinx.serialization.builtins.serializer
|
||||
|
||||
|
||||
@ExperimentalUnsignedTypes
|
||||
fun main(args: Array<String>) {
|
||||
|
||||
val config = KxsTsConfig(
|
||||
// typeAliasTyping = BrandTyping
|
||||
)
|
||||
|
||||
val gen2 = KxsTsGenerator(config)
|
||||
|
||||
gen2.descriptorOverrides += mapOf(
|
||||
//@formatter:off
|
||||
Float .serializer().descriptor to TsTypeAlias(TsElementId("kotlin.Float" ), TsTypeRef.Literal(TsLiteral.Custom("float" ), false)),
|
||||
Double.serializer().descriptor to TsTypeAlias(TsElementId("kotlin.Double"), TsTypeRef.Literal(TsLiteral.Custom("double"), false)),
|
||||
Int .serializer().descriptor to TsTypeAlias(TsElementId("kotlin.Int" ), TsTypeRef.Literal(TsLiteral.Custom("int" ), false)),
|
||||
Byte .serializer().descriptor to TsTypeAlias(TsElementId("kotlin.Byte" ), TsTypeRef.Literal(TsLiteral.Custom("int8" ), false)),
|
||||
UInt .serializer().descriptor to TsTypeAlias(TsElementId("kotlin.UInt" ), TsTypeRef.Literal(TsLiteral.Custom("uint" ), false)),
|
||||
UByte .serializer().descriptor to TsTypeAlias(TsElementId("kotlin.UByte" ), TsTypeRef.Literal(TsLiteral.Custom("uint8" ), false)),
|
||||
UShort.serializer().descriptor to TsTypeAlias(TsElementId("kotlin.UShort"), TsTypeRef.Literal(TsLiteral.Custom("uint16"), false)),
|
||||
ULong .serializer().descriptor to TsTypeAlias(TsElementId("kotlin.ULong" ), TsTypeRef.Literal(TsLiteral.Custom("uint64"), false)),
|
||||
//@formatter:on
|
||||
)
|
||||
|
||||
val definitions = gen2.generate(
|
||||
KafkatorioPacket2.serializer(),
|
||||
// KafkatorioKeyedPacketKey2.serializer(),
|
||||
)
|
||||
|
||||
|
||||
when (val filename = args.firstOrNull()) {
|
||||
null -> println(definitions)
|
||||
else -> {
|
||||
val outPath = Path(filename)
|
||||
outPath.createDirectories()
|
||||
val outFile = outPath.resolve("kafkatorio-schema.ts").toFile()
|
||||
outFile.printWriter().use {
|
||||
it.println(kt2tsHeader)
|
||||
it.print(definitions)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue