Render buildings and resources
- create LuaJsonList to handle empty Lua JSON lists being sent as empty objects - fix exporting of resources and entities - split up map tiles into different layer directories - add 'chunk size' to MapChunkPosition - move tests to correct packages - bump tstl - decrease tstl logging - remove some defined, but unused, kafka topics - syslog, rest, websocket servers & web-map: decrease logging - webmap: add additional layers for buildings and resources
This commit is contained in:
parent
a805d2ad63
commit
b507081eac
72 changed files with 2167 additions and 1068 deletions
|
|
@ -1,19 +1,17 @@
|
|||
package dev.adamko.kafkatorio.library
|
||||
|
||||
import kotlin.reflect.KClass
|
||||
import kotlinx.serialization.DeserializationStrategy
|
||||
import kotlin.jvm.JvmInline
|
||||
import kotlinx.serialization.KSerializer
|
||||
import kotlinx.serialization.Serializable
|
||||
import kotlinx.serialization.SerializationException
|
||||
import kotlinx.serialization.builtins.ListSerializer
|
||||
import kotlinx.serialization.descriptors.SerialDescriptor
|
||||
import kotlinx.serialization.encoding.Decoder
|
||||
import kotlinx.serialization.encoding.Encoder
|
||||
import kotlinx.serialization.json.Json
|
||||
import kotlinx.serialization.json.JsonArray
|
||||
import kotlinx.serialization.json.JsonContentPolymorphicSerializer
|
||||
import kotlinx.serialization.json.JsonElement
|
||||
import kotlinx.serialization.json.JsonDecoder
|
||||
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
|
||||
|
||||
|
||||
|
|
@ -22,55 +20,60 @@ val jsonMapperKafkatorio = Json {
|
|||
prettyPrintIndent = " "
|
||||
|
||||
serializersModule = SerializersModule {
|
||||
contextual(List::class) { args -> FactorioJsonListSerializer(args[0]) }
|
||||
// contextual(List::class) { args -> FactorioListJsonSerializer(args[0]) }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/** @see LuaJsonListSerializer */
|
||||
@Serializable(with = LuaJsonListSerializer::class)
|
||||
@JvmInline
|
||||
value class LuaJsonList<T>(private val list: List<T>) : List<T> by list
|
||||
|
||||
|
||||
/**
|
||||
* Factorio outputs an *empty* list as an *empty* Json object. This serde performs the same
|
||||
* conversion.
|
||||
* Factorio outputs **empty** lists `[]` as *empty* Json objects `{}`.
|
||||
*
|
||||
* When deserializing JSON, this serializer transforms any incoming empty objects to empty
|
||||
* [array][JsonArray]s.
|
||||
*/
|
||||
class FactorioJsonListSerializer<T>(dataSerializer: KSerializer<T>) :
|
||||
JsonTransformingSerializer<List<T>>(ListSerializer(dataSerializer)) {
|
||||
class LuaJsonListSerializer<T>(
|
||||
elementSerializer: KSerializer<T>
|
||||
) : KSerializer<LuaJsonList<T>> {
|
||||
|
||||
override fun transformDeserialize(element: JsonElement): JsonElement {
|
||||
return when (element) {
|
||||
is JsonObject -> JsonArray(emptyList())
|
||||
else -> element
|
||||
private val listSerializer: KSerializer<List<T>> = ListSerializer(elementSerializer)
|
||||
|
||||
override val descriptor: SerialDescriptor =
|
||||
SerialDescriptor("kafkatorio.LuaList", listSerializer.descriptor)
|
||||
|
||||
override fun deserialize(decoder: Decoder): LuaJsonList<T> = LuaJsonList(
|
||||
when (decoder) {
|
||||
is JsonDecoder -> deserializeJson(decoder)
|
||||
else -> decoder.decodeSerializableValue(listSerializer)
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
override fun transformSerialize(element: JsonElement): JsonElement {
|
||||
return when {
|
||||
element is JsonArray && element.isEmpty() -> buildJsonObject { }
|
||||
else -> element
|
||||
private fun deserializeJson(decoder: JsonDecoder): List<T> =
|
||||
when (val element = decoder.decodeJsonElement()) {
|
||||
is JsonObject -> when {
|
||||
element.entries.isEmpty() -> emptyList()
|
||||
else -> deserializeError(
|
||||
"expected empty Json object, but contained ${element.entries.size} entries ${element.entries}"
|
||||
)
|
||||
}
|
||||
is JsonArray -> decoder.json.decodeFromJsonElement(listSerializer, element)
|
||||
else -> deserializeError(
|
||||
"expected empty Json object or a Json array, but was $element"
|
||||
)
|
||||
}
|
||||
|
||||
override fun serialize(encoder: Encoder, value: LuaJsonList<T>) {
|
||||
encoder.encodeSerializableValue(listSerializer, value)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
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
|
||||
companion object {
|
||||
private fun deserializeError(msg: String): Nothing = throw SerializationException(
|
||||
"Error deserializing LuaList: $msg"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,103 @@
|
|||
package dev.adamko.kafkatorio.library
|
||||
|
||||
import dev.adamko.kafkatorio.schema.common.ChunkSize
|
||||
import dev.adamko.kafkatorio.schema.common.MapChunkPosition
|
||||
import dev.adamko.kafkatorio.schema.common.MapEntityPosition
|
||||
import dev.adamko.kafkatorio.schema.common.MapTilePosition
|
||||
import kotlin.math.floor
|
||||
|
||||
|
||||
operator fun MapTilePosition.times(factor: Int): MapTilePosition =
|
||||
MapTilePosition(x * factor, y * factor)
|
||||
|
||||
|
||||
operator fun MapTilePosition.div(divisor: Int): MapTilePosition =
|
||||
MapTilePosition(x / divisor, y / divisor)
|
||||
|
||||
|
||||
operator fun MapTilePosition.plus(addend: Int): MapTilePosition =
|
||||
MapTilePosition(x + addend, y + addend)
|
||||
|
||||
|
||||
operator fun MapTilePosition.plus(addend: MapTilePosition): MapTilePosition =
|
||||
MapTilePosition(x + addend.x, y + addend.y)
|
||||
|
||||
|
||||
operator fun MapTilePosition.minus(subtrahend: MapTilePosition): MapTilePosition =
|
||||
MapTilePosition(x - subtrahend.x, y - subtrahend.y)
|
||||
|
||||
|
||||
/** Get the [MapChunkPosition] of the tile */
|
||||
fun MapTilePosition.toMapChunkPosition(chunkSize: ChunkSize): MapChunkPosition =
|
||||
MapChunkPosition(
|
||||
x = floor(x.toDouble() / chunkSize.lengthInTiles.toDouble()).toInt(),
|
||||
y = floor(y.toDouble() / chunkSize.lengthInTiles.toDouble()).toInt(),
|
||||
chunkSize = chunkSize,
|
||||
)
|
||||
|
||||
|
||||
/** Get the [MapTilePosition] of the entity */
|
||||
fun MapEntityPosition.toMapTilePosition(): MapTilePosition =
|
||||
MapTilePosition(
|
||||
x = floor(x).toInt(),
|
||||
y = floor(y).toInt(),
|
||||
)
|
||||
|
||||
|
||||
/** Get the [MapChunkPosition] of the entity */
|
||||
fun MapEntityPosition.toMapChunkPosition(chunkSize: ChunkSize): MapChunkPosition =
|
||||
MapChunkPosition(
|
||||
x = floor(x / chunkSize.lengthInTiles.toDouble()).toInt(),
|
||||
y = floor(y / chunkSize.lengthInTiles.toDouble()).toInt(),
|
||||
chunkSize = chunkSize,
|
||||
)
|
||||
|
||||
|
||||
fun MapChunkPosition.resize(chunkSize: ChunkSize): MapChunkPosition =
|
||||
leftTopTile().toMapChunkPosition(chunkSize)
|
||||
|
||||
|
||||
operator fun MapChunkPosition.times(factor: Int): MapChunkPosition =
|
||||
copy(x = x * factor, y = y * factor)
|
||||
|
||||
|
||||
operator fun MapChunkPosition.plus(addend: Int): MapChunkPosition =
|
||||
copy(x = x + addend, y = y + addend)
|
||||
|
||||
|
||||
operator fun MapChunkPosition.minus(subtrahend: Int): MapChunkPosition =
|
||||
copy(x = x - subtrahend, y = y - subtrahend)
|
||||
|
||||
|
||||
fun MapChunkPosition.leftTopTile(): MapTilePosition =
|
||||
MapTilePosition(
|
||||
x * chunkSize.lengthInTiles,
|
||||
y * chunkSize.lengthInTiles,
|
||||
)
|
||||
|
||||
|
||||
fun MapChunkPosition.rightBottomTile(): MapTilePosition =
|
||||
leftTopTile() + (chunkSize.lengthInTiles - 1)
|
||||
|
||||
|
||||
/** Iterate over all possible [MapTilePosition]s in the chunk. */
|
||||
fun MapChunkPosition.iterateTiles(): Iterator<MapTilePosition> {
|
||||
val leftTop = leftTopTile()
|
||||
val rightBottom = rightBottomTile()
|
||||
|
||||
return iterator {
|
||||
(leftTop.x..rightBottom.x).forEach { x ->
|
||||
(leftTop.y..rightBottom.y).forEach { y ->
|
||||
yield(MapTilePosition(x, y))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//val MapChunkPosition.rightBottomTile: MapTilePosition
|
||||
// get() = leftTopTile + (MAP_CHUNK_SIZE - 1)
|
||||
|
||||
//
|
||||
//val MapChunkPosition.boundingBox: MapBoundingBox
|
||||
// get() = MapBoundingBox(leftTopTile, rightBottomTile)
|
||||
|
|
@ -52,6 +52,7 @@ data class ColourHex(
|
|||
) {
|
||||
companion object {
|
||||
val TRANSPARENT = ColourHex(0u, 0u, 0u, 0u)
|
||||
val WHITE = ColourHex(UByte.MAX_VALUE, UByte.MAX_VALUE, UByte.MAX_VALUE, UByte.MAX_VALUE)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,24 +1,29 @@
|
|||
package dev.adamko.kafkatorio.schema.common
|
||||
|
||||
import kotlin.jvm.JvmInline
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
|
||||
// TODO change all booleans to be optional, and default to 'false' (also in events-mod)
|
||||
|
||||
|
||||
@Serializable
|
||||
@SerialName("kafkatorio.entity.FactorioEntityData")
|
||||
sealed interface FactorioEntityData {
|
||||
|
||||
val protoId: PrototypeId
|
||||
val status: Status?
|
||||
|
||||
val position: MapEntityPosition
|
||||
|
||||
/** Non-specific entity data */
|
||||
@Serializable
|
||||
@SerialName("kafkatorio.entity.FactorioEntityData.Standard")
|
||||
@SerialName("kafkatorio.entity.FactorioEntityData")
|
||||
data class Standard(
|
||||
override val protoId: PrototypeId,
|
||||
override val status: Status? = null,
|
||||
val status: Status? = null,
|
||||
|
||||
val unitNumber: UnitNumber? = null,
|
||||
|
||||
override val position: MapEntityPosition,
|
||||
val graphicsVariation: UByte? = null,
|
||||
|
|
@ -26,121 +31,230 @@ sealed interface FactorioEntityData {
|
|||
val isActive: Boolean? = null,
|
||||
val isRotatable: Boolean? = null,
|
||||
val lastUser: UInt? = null,
|
||||
val localisedDescription: String? = null,
|
||||
val localisedName: String? = null,
|
||||
) : FactorioEntityData
|
||||
) : FactorioEntityData {
|
||||
constructor(
|
||||
position: MapEntityPosition,
|
||||
protoId: PrototypeId,
|
||||
element: FactorioEntityUpdateElement.Entity
|
||||
) : this(
|
||||
protoId = protoId,
|
||||
position = position,
|
||||
|
||||
status = element.status,
|
||||
unitNumber = element.unitNumber,
|
||||
graphicsVariation = element.graphicsVariation,
|
||||
health = element.health,
|
||||
isActive = element.isActive,
|
||||
isRotatable = element.isRotatable,
|
||||
lastUser = element.lastUser
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@Serializable
|
||||
@SerialName("kafkatorio.entity.FactorioEntityData.Resource")
|
||||
@SerialName("kafkatorio.entity.FactorioResourceData")
|
||||
data class Resource(
|
||||
override val protoId: PrototypeId,
|
||||
override val status: Status? = null,
|
||||
|
||||
override val position: MapEntityPosition,
|
||||
val amount: UInt,
|
||||
/** Count of initial resource units contained. `null` if resource is not infinite. */
|
||||
val initialAmount: UInt? = null,
|
||||
) : FactorioEntityData
|
||||
) : FactorioEntityData {
|
||||
constructor(
|
||||
position: MapEntityPosition,
|
||||
protoId: PrototypeId,
|
||||
element: FactorioEntityUpdateElement.Resource
|
||||
) : this(
|
||||
protoId = protoId,
|
||||
position = position,
|
||||
|
||||
amount = element.amount,
|
||||
initialAmount = element.initialAmount,
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* [`https://lua-api.factorio.com/latest/defines.html#defines.entity_status`](https://lua-api.factorio.com/latest/defines.html#defines.entity_status)
|
||||
*/
|
||||
@Suppress("unused") // not directly used at the moment - maybe later
|
||||
@Serializable
|
||||
@SerialName("kafkatorio.entity.FactorioEntityData.EntityStatus")
|
||||
@SerialName("kafkatorio.entity.FactorioEntityData.FactorioEntityStatus")
|
||||
enum class Status {
|
||||
CANT_DIVIDE_SEGMENTS,
|
||||
CHARGING,
|
||||
CLOSED_BY_CIRCUIT_NETWORK,
|
||||
DISABLED,
|
||||
DISABLED_BY_CONTROL_BEHAVIOR,
|
||||
DISABLED_BY_SCRIPT,
|
||||
DISCHARGING,
|
||||
FLUID_INGREDIENT_SHORTAGE,
|
||||
FULLY_CHARGED,
|
||||
FULL_OUTPUT,
|
||||
ITEM_INGREDIENT_SHORTAGE,
|
||||
LAUNCHING_ROCKET,
|
||||
LOW_INPUT_FLUID,
|
||||
LOW_POWER,
|
||||
LOW_TEMPERATURE,
|
||||
MARKED_FOR_DECONSTRUCTION,
|
||||
MISSING_REQUIRED_FLUID,
|
||||
MISSING_SCIENCE_PACKS,
|
||||
NETWORKS_CONNECTED,
|
||||
NETWORKS_DISCONNECTED,
|
||||
NORMAL,
|
||||
NOT_CONNECTED_TO_RAIL,
|
||||
NOT_PLUGGED_IN_ELECTRIC_NETWORK,
|
||||
NO_AMMO,
|
||||
NO_FUEL,
|
||||
NO_INGREDIENTS,
|
||||
NO_INPUT_FLUID,
|
||||
NO_MINABLE_RESOURCES,
|
||||
NO_MODULES_TO_TRANSMIT,
|
||||
NO_POWER,
|
||||
NO_RECIPE,
|
||||
NO_RESEARCH_IN_PROGRESS,
|
||||
OPENED_BY_CIRCUIT_NETWORK,
|
||||
OUT_OF_LOGISTIC_NETWORK,
|
||||
PREPARING_ROCKET_FOR_LAUNCH,
|
||||
RECHARGING_AFTER_POWER_OUTAGE,
|
||||
TURNED_OFF_DURING_DAYTIME,
|
||||
WAITING_FOR_SOURCE_ITEMS,
|
||||
WAITING_FOR_SPACE_IN_DESTINATION,
|
||||
WAITING_FOR_TARGET_TO_BE_BUILT,
|
||||
WAITING_FOR_TRAIN,
|
||||
WAITING_TO_LAUNCH_ROCKET,
|
||||
WORKING,
|
||||
NORMAL,
|
||||
NO_POWER,
|
||||
LOW_POWER,
|
||||
NO_FUEL,
|
||||
DISABLED_BY_CONTROL_BEHAVIOR,
|
||||
OPENED_BY_CIRCUIT_NETWORK,
|
||||
CLOSED_BY_CIRCUIT_NETWORK,
|
||||
DISABLED_BY_SCRIPT,
|
||||
MARKED_FOR_DECONSTRUCTION,
|
||||
|
||||
/** Used by generators and solar panels. */
|
||||
NOT_PLUGGED_IN_ELECTRIC_NETWORK,
|
||||
|
||||
/** Used by power switches. */
|
||||
NETWORKS_CONNECTED,
|
||||
|
||||
/** Used by power switches. */
|
||||
NETWORKS_DISCONNECTED,
|
||||
|
||||
/** Used by accumulators. */
|
||||
CHARGING,
|
||||
/** Used by accumulators. */
|
||||
DISCHARGING,
|
||||
/** Used by accumulators. */
|
||||
FULLY_CHARGED,
|
||||
|
||||
/** Used by logistic containers. */
|
||||
OUT_OF_LOGISTIC_NETWORK,
|
||||
|
||||
/** Used by assembling machines. */
|
||||
NO_RECIPE,
|
||||
|
||||
/** Used by furnaces. */
|
||||
NO_INGREDIENTS,
|
||||
|
||||
/** USED BY BOILERS, fluid turrets and fluid energy sources: Boiler has no fluid to work with. */
|
||||
NO_INPUT_FLUID,
|
||||
|
||||
/** Used by labs. */
|
||||
NO_RESEARCH_IN_PROGRESS,
|
||||
|
||||
/** Used by mining drills. */
|
||||
NO_MINABLE_RESOURCES,
|
||||
|
||||
/** Used by boilers and fluid turrets: Boiler still has some fluid but is about to run out. */
|
||||
LOW_INPUT_FLUID,
|
||||
|
||||
/** Used by crafting machines. */
|
||||
FLUID_INGREDIENT_SHORTAGE,
|
||||
|
||||
/**
|
||||
* Used by crafting machines, boilers, burner energy sources and reactors: Reactor/burner has
|
||||
* full burnt result inventory, boiler has full output fluid box.
|
||||
*/
|
||||
FULL_OUTPUT,
|
||||
|
||||
/** Used by crafting machines. */
|
||||
ITEM_INGREDIENT_SHORTAGE,
|
||||
|
||||
/** Used by mining drills when the mining fluid is missing. */
|
||||
MISSING_REQUIRED_FLUID,
|
||||
|
||||
/** Used by labs. */
|
||||
MISSING_SCIENCE_PACKS,
|
||||
|
||||
/** Used by inserters. */
|
||||
WAITING_FOR_SOURCE_ITEMS,
|
||||
|
||||
/** Used by inserters and mining drills. */
|
||||
WAITING_FOR_SPACE_IN_DESTINATION,
|
||||
|
||||
/** Used by the rocket silo. */
|
||||
PREPARING_ROCKET_FOR_LAUNCH,
|
||||
/** Used by the rocket silo. */
|
||||
WAITING_TO_LAUNCH_ROCKET,
|
||||
/** Used by the rocket silo. */
|
||||
LAUNCHING_ROCKET,
|
||||
|
||||
/** Used by beacons. */
|
||||
NO_MODULES_TO_TRANSMIT,
|
||||
|
||||
/** Used by roboports. */
|
||||
RECHARGING_AFTER_POWER_OUTAGE,
|
||||
|
||||
/** Used by inserters targeting entity ghosts. */
|
||||
WAITING_FOR_TARGET_TO_BE_BUILT,
|
||||
/** Used by inserters targeting rails. */
|
||||
WAITING_FOR_TRAIN,
|
||||
|
||||
/** Used by ammo turrets. */
|
||||
NO_AMMO,
|
||||
|
||||
/** Used by heat energy sources. */
|
||||
LOW_TEMPERATURE,
|
||||
|
||||
/** Used by constant combinators: Combinator is turned off via switch in GUI. */
|
||||
DISABLED,
|
||||
|
||||
/** Used by lamps. */
|
||||
TURNED_OFF_DURING_DAYTIME,
|
||||
|
||||
/** Used by rail signals. */
|
||||
NOT_CONNECTED_TO_RAIL,
|
||||
/** Used by rail signals. */
|
||||
CANT_DIVIDE_SEGMENTS,
|
||||
}
|
||||
|
||||
}
|
||||
/*
|
||||
enum entity_status {
|
||||
working,
|
||||
normal,
|
||||
no_power,
|
||||
low_power,
|
||||
no_fuel,
|
||||
disabled_by_control_behavior,
|
||||
opened_by_circuit_network,
|
||||
closed_by_circuit_network,
|
||||
disabled_by_script,
|
||||
marked_for_deconstruction,
|
||||
not_plugged_in_electric_network,
|
||||
networks_connected,
|
||||
networks_disconnected,
|
||||
charging,
|
||||
discharging,
|
||||
fully_charged,
|
||||
out_of_logistic_network,
|
||||
no_recipe,
|
||||
no_ingredients,
|
||||
no_input_fluid,
|
||||
no_research_in_progress,
|
||||
no_minable_resources,
|
||||
low_input_fluid,
|
||||
fluid_ingredient_shortage,
|
||||
full_output,
|
||||
item_ingredient_shortage,
|
||||
missing_required_fluid,
|
||||
missing_science_packs,
|
||||
waiting_for_source_items,
|
||||
waiting_for_space_in_destination,
|
||||
preparing_rocket_for_launch,
|
||||
waiting_to_launch_rocket,
|
||||
launching_rocket,
|
||||
no_modules_to_transmit,
|
||||
recharging_after_power_outage,
|
||||
waiting_for_target_to_be_built,
|
||||
waiting_for_train,
|
||||
no_ammo,
|
||||
low_temperature,
|
||||
disabled,
|
||||
turned_off_during_daytime,
|
||||
not_connected_to_rail,
|
||||
cant_divide_segments,
|
||||
}
|
||||
|
||||
*/
|
||||
|
||||
sealed interface FactorioEntityUpdateDictionary<T : FactorioEntityUpdateElement>
|
||||
: Iterable<Pair<MapEntityPosition, T>> {
|
||||
|
||||
val entitiesXY: Map<String, Map<String, T>>
|
||||
|
||||
operator fun get(position: MapEntityPosition): T? =
|
||||
entitiesXY[position.x.toString()]?.get(position.y.toString())
|
||||
|
||||
override fun iterator(): Iterator<Pair<MapEntityPosition, T>> =
|
||||
iterator {
|
||||
entitiesXY.forEach { (x, column) ->
|
||||
column.forEach { (y, entity) ->
|
||||
yield(
|
||||
MapEntityPosition(x.toDouble(), y.toDouble()) to entity
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Serializable
|
||||
@SerialName("kafkatorio.entity.FactorioEntityUpdateResourceDictionary")
|
||||
@JvmInline
|
||||
value class Resource(
|
||||
override val entitiesXY: Map<String, Map<String, FactorioEntityUpdateElement.Resource>>
|
||||
) : FactorioEntityUpdateDictionary<FactorioEntityUpdateElement.Resource>
|
||||
|
||||
@Serializable
|
||||
@SerialName("kafkatorio.entity.FactorioEntityUpdateEntityDictionary")
|
||||
@JvmInline
|
||||
value class Entity(
|
||||
override val entitiesXY: Map<String, Map<String, FactorioEntityUpdateElement.Entity>>
|
||||
) : FactorioEntityUpdateDictionary<FactorioEntityUpdateElement.Entity>
|
||||
|
||||
}
|
||||
|
||||
|
||||
sealed interface FactorioEntityUpdateElement {
|
||||
|
||||
@Serializable
|
||||
@SerialName("kafkatorio.entity.FactorioEntityUpdateResource")
|
||||
data class Resource(
|
||||
val amount: UInt,
|
||||
/** Count of initial resource units contained. `null` if resource is not infinite. */
|
||||
val initialAmount: UInt? = null,
|
||||
) : FactorioEntityUpdateElement
|
||||
|
||||
@Serializable
|
||||
@SerialName("kafkatorio.entity.FactorioEntityUpdateEntity")
|
||||
data class Entity(
|
||||
val status: FactorioEntityData.Status? = null,
|
||||
|
||||
val unitNumber: UnitNumber? = null,
|
||||
|
||||
val graphicsVariation: UByte? = null,
|
||||
val health: Float? = null,
|
||||
val isActive: Boolean? = null,
|
||||
val isRotatable: Boolean? = null,
|
||||
val lastUser: UInt? = null,
|
||||
) : FactorioEntityUpdateElement
|
||||
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
AssemblingMachine
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
package dev.adamko.kafkatorio.schema.common
|
||||
|
||||
import kotlinx.serialization.Contextual
|
||||
import dev.adamko.kafkatorio.library.LuaJsonList
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
|
|
@ -20,8 +20,8 @@ sealed interface FactorioPrototype {
|
|||
val mapColour: Colour,
|
||||
|
||||
val layer: UInt,
|
||||
@Contextual
|
||||
val collisionMasks: List<String>,
|
||||
// @Contextual
|
||||
val collisionMasks: LuaJsonList<String>,
|
||||
val order: String,
|
||||
/** Can the tile be mined for resources? */
|
||||
val canBeMined: Boolean,
|
||||
|
|
@ -49,17 +49,23 @@ sealed interface FactorioPrototype {
|
|||
val mapColourEnemy: Colour? = null,
|
||||
|
||||
val maxHealth: Float,
|
||||
val isBuilding: Boolean,
|
||||
val isEntityWithOwner: Boolean,
|
||||
val isMilitaryTarget: Boolean,
|
||||
val isBuilding: Boolean = false,
|
||||
val isEntityWithOwner: Boolean = false,
|
||||
val isMilitaryTarget: Boolean = false,
|
||||
val miningProperties: MiningProperties? = null,
|
||||
|
||||
val collisionBox: MapBoundingBox? = null,
|
||||
) : FactorioPrototype {
|
||||
|
||||
val tileHeight: Int? get() = collisionBox?.tileHeight
|
||||
val tileWidth: Int? get() = collisionBox?.tileWidth
|
||||
|
||||
@Serializable
|
||||
@SerialName("kafkatorio.prototype.EntityMiningProperties")
|
||||
data class MiningProperties(
|
||||
val canBeMined: Boolean,
|
||||
val products: List<MinedProduct>?,
|
||||
// @Contextual
|
||||
val products: LuaJsonList<MinedProduct>?,
|
||||
)
|
||||
|
||||
@Serializable
|
||||
|
|
|
|||
|
|
@ -1,10 +1,11 @@
|
|||
package dev.adamko.kafkatorio.schema.common
|
||||
|
||||
|
||||
import dev.adamko.kafkatorio.schema.common.MapTileDictionary.PrototypeKey
|
||||
import kotlin.jvm.JvmInline
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
|
||||
const val MAP_CHUNK_SIZE = 32
|
||||
|
||||
|
||||
|
|
@ -24,11 +25,11 @@ data class MapTile(
|
|||
* [PrototypeKey] is an arbitrary integer value, which maps to one [PrototypeId] only for a
|
||||
* specific instance of [MapTileDictionary].
|
||||
*
|
||||
* Each X/Y coordinate in [tilesXY] maps to a [PrototypeKey].
|
||||
* @param[tilesXY] Map an X/Y coordinate to a [PrototypeKey].
|
||||
*/
|
||||
@Serializable
|
||||
@SerialName("kafkatorio.packet.keyed.MapTileDictionary")
|
||||
data class MapTileDictionary(
|
||||
/** Map an X,Y coordinate a prototype name */
|
||||
val tilesXY: Map<String, Map<String, PrototypeKey>>,
|
||||
val protos: Map<PrototypeId, PrototypeKey>,
|
||||
) {
|
||||
|
|
@ -55,59 +56,68 @@ data class MapTileDictionary(
|
|||
/** An arbitrary ID for a prototype in [MapTileDictionary.protos]. */
|
||||
@Serializable
|
||||
@JvmInline
|
||||
value class PrototypeKey(private val index: Int)
|
||||
value class PrototypeKey(private val index: Int) : Comparable<Int> by index
|
||||
|
||||
companion object {
|
||||
fun MapTileDictionary?.isNullOrEmpty(): Boolean {
|
||||
return this == null || (tilesXY.isEmpty() && tilesXY.all { it.value.isEmpty() })
|
||||
}
|
||||
}
|
||||
// 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,
|
||||
)
|
||||
|
||||
|
||||
@Serializable
|
||||
data class ServerMapChunkId(
|
||||
val serverId: FactorioServerId,
|
||||
val layer: ServerMapTileLayer,
|
||||
val chunkPosition: MapChunkPosition,
|
||||
val surfaceIndex: SurfaceIndex,
|
||||
val chunkSize: ChunkSize,
|
||||
)
|
||||
) {
|
||||
val chunkSize: ChunkSize get() = chunkPosition.chunkSize
|
||||
}
|
||||
|
||||
|
||||
/** `${serverDataDir}/servers/{serverId}/map/tiles/s{surfaceIndex}/z{zoomLevel}/x{chunkX}/y{chunkY}.png` */
|
||||
@Serializable
|
||||
enum class ServerMapTileLayer(val dir: String) {
|
||||
@SerialName("terrain")
|
||||
TERRAIN("terrain"),
|
||||
@SerialName("resource")
|
||||
RESOURCE("resource"),
|
||||
@SerialName("building")
|
||||
BUILDING("building"),
|
||||
}
|
||||
|
||||
|
||||
/** `${serverDataDir}/servers/{serverId}/map/${layer}/s{surfaceIndex}/z{zoomLevel}/x{chunkX}/y{chunkY}.png` */
|
||||
@Serializable
|
||||
@JvmInline
|
||||
value class TilePngFilename(
|
||||
val value: String,
|
||||
) {
|
||||
value class ServerMapTilePngFilename(val value: String) {
|
||||
|
||||
constructor(id: ServerMapChunkId) : this(
|
||||
id.serverId,
|
||||
id.surfaceIndex,
|
||||
id.chunkSize.zoomLevel,
|
||||
id.chunkPosition.x,
|
||||
id.chunkPosition.y,
|
||||
serverId = id.serverId,
|
||||
layer = id.layer,
|
||||
surfaceIndex = id.surfaceIndex,
|
||||
zoomLevel = id.chunkSize.zoomLevel,
|
||||
chunkX = id.chunkPosition.x,
|
||||
chunkY = id.chunkPosition.y,
|
||||
)
|
||||
|
||||
constructor(
|
||||
serverId: FactorioServerId,
|
||||
layer: ServerMapTileLayer,
|
||||
surfaceIndex: SurfaceIndex,
|
||||
zoomLevel: Int,
|
||||
chunkX: Int,
|
||||
chunkY: Int,
|
||||
) : this(buildString {
|
||||
append("servers/${serverId}")
|
||||
append("/map/tiles")
|
||||
append("/s${surfaceIndex}")
|
||||
append("/z${zoomLevel}")
|
||||
append("/x${chunkX}")
|
||||
append("/y${chunkY}")
|
||||
append(".png")
|
||||
})
|
||||
) : this(
|
||||
"servers/${serverId}" +
|
||||
"/map" +
|
||||
"/layers/${layer.dir}" +
|
||||
"/s${surfaceIndex}" +
|
||||
"/z${zoomLevel}" +
|
||||
"/x${chunkX}" +
|
||||
"/y${chunkY}" +
|
||||
".png"
|
||||
)
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,83 +1,171 @@
|
|||
package dev.adamko.kafkatorio.schema.common
|
||||
|
||||
import dev.adamko.kxstsgen.core.*
|
||||
import dev.adamko.kafkatorio.library.leftTopTile
|
||||
import dev.adamko.kafkatorio.library.rightBottomTile
|
||||
import dev.adamko.kxstsgen.core.experiments.TupleSerializer
|
||||
import kotlin.math.floor
|
||||
import kotlin.math.abs
|
||||
import kotlin.math.ceil
|
||||
import kotlin.math.pow
|
||||
import kotlin.math.roundToInt
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
|
||||
/**
|
||||
* Coordinates of a [EntityData] on a map.
|
||||
* The position of something that exists in-game (not conceptually, like a
|
||||
* [chunk][MapChunkPosition])
|
||||
*/
|
||||
sealed interface MapPosition : Comparable<MapPosition> {
|
||||
val x: Number
|
||||
val y: Number
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Coordinates of an [entity][FactorioEntityData.Standard] on a map.
|
||||
*
|
||||
* Positive y points south, positive x points east.
|
||||
*
|
||||
* A [MapEntityPosition] can be translated to a [MapChunkPosition] by dividing the `x`/`y` values
|
||||
* by [32][MAP_CHUNK_SIZE].
|
||||
*/
|
||||
@Serializable(with = MapEntityPosition.Serializer::class)
|
||||
@Serializable(with = MapEntityPosition.Companion.Serializer::class)
|
||||
data class MapEntityPosition(
|
||||
val x: Double,
|
||||
val y: Double,
|
||||
) {
|
||||
object Serializer : TupleSerializer<MapEntityPosition>(
|
||||
"MapEntityPosition",
|
||||
{
|
||||
element(MapEntityPosition::x)
|
||||
element(MapEntityPosition::y)
|
||||
override val x: Double,
|
||||
override val y: Double,
|
||||
) : MapPosition {
|
||||
|
||||
|
||||
override fun compareTo(other: MapPosition): Int =
|
||||
when (val resultX = x.compareTo(other.x.toDouble())) {
|
||||
0 -> y.compareTo(other.y.toDouble())
|
||||
else -> resultX
|
||||
}
|
||||
) {
|
||||
override fun tupleConstructor(elements: Iterator<*>): MapEntityPosition {
|
||||
return MapEntityPosition(
|
||||
elements.next() as Double,
|
||||
elements.next() as Double,
|
||||
)
|
||||
|
||||
|
||||
companion object {
|
||||
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
|
||||
/**
|
||||
* Coordinates of a tile on a surface, where each `x`/`y` coordinate represents a different
|
||||
* [MapTile].
|
||||
*
|
||||
* Positive y points south, positive x points east.
|
||||
*
|
||||
* It rounds any `x`/`y` down to whole numbers.
|
||||
*/
|
||||
@Serializable(with = MapTilePosition.Companion.Serializer::class)
|
||||
data class MapTilePosition(
|
||||
override val x: Int,
|
||||
override val y: Int,
|
||||
) : MapPosition {
|
||||
|
||||
|
||||
override fun compareTo(other: MapPosition): Int =
|
||||
when (val resultX = x.compareTo(other.x.toInt())) {
|
||||
0 -> y.compareTo(other.y.toInt())
|
||||
else -> resultX
|
||||
}
|
||||
|
||||
|
||||
companion object {
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Coordinates of a chunk on a surface, where each `x`/`y` coordinate represents a different
|
||||
* chunk.
|
||||
*
|
||||
* Positive y points south, positive x points east.
|
||||
*
|
||||
* A [MapChunkPosition] can be translated to a [MapEntityPosition] by multiplying the `x`/`y`
|
||||
* values by [32][MAP_CHUNK_SIZE].
|
||||
*/
|
||||
@Serializable(with = MapChunkPosition.Serializer::class)
|
||||
@Serializable(with = MapChunkPosition.Companion.Serializer::class)
|
||||
data class MapChunkPosition(
|
||||
val x: Int,
|
||||
val y: Int,
|
||||
// Don't set a default for ChunkSize. It could default to 32, but it's clearer to make it explicit
|
||||
// and avoid accidentally not setting it. Also, a default doesn't save much in serialization size.
|
||||
val chunkSize: ChunkSize,
|
||||
) {
|
||||
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,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private val bounds: MapChunkPositionBounds by lazy {
|
||||
MapChunkPositionBounds(this, ChunkSize.CHUNK_032)
|
||||
val bounds: MapChunkPositionBounds by lazy {
|
||||
MapChunkPositionBounds(this)
|
||||
}
|
||||
|
||||
operator fun contains(tilePosition: MapTilePosition): Boolean = tilePosition in bounds
|
||||
operator fun contains(entityPosition: MapEntityPosition): Boolean = entityPosition in bounds
|
||||
|
||||
companion object {
|
||||
object Serializer : TupleSerializer<MapChunkPosition>(
|
||||
"MapChunkPosition",
|
||||
{
|
||||
element(MapChunkPosition::x)
|
||||
element(MapChunkPosition::y)
|
||||
element(MapChunkPosition::chunkSize)
|
||||
}
|
||||
) {
|
||||
override fun tupleConstructor(elements: Iterator<*>): MapChunkPosition {
|
||||
return MapChunkPosition(
|
||||
elements.next() as Int,
|
||||
elements.next() as Int,
|
||||
elements.next() as ChunkSize,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* The bounds of a [MapChunkPosition], that may contain a [MapPosition].
|
||||
*
|
||||
* Because Factorio uses screen-coordinates, the lowest `x/y` coordinate is the [leftTop], and
|
||||
* the highest is [rightBottomTile].
|
||||
*/
|
||||
@Serializable
|
||||
data class MapChunkPositionBounds(
|
||||
val leftTop: MapTilePosition,
|
||||
val rightBottom: MapTilePosition,
|
||||
) : ClosedRange<MapTilePosition> {
|
||||
val chunkSize: ChunkSize, // should be computable based on the tiles, but include it for completeness
|
||||
) : ClosedRange<MapPosition> {
|
||||
|
||||
constructor(chunkPosition: MapChunkPosition, chunkSize: ChunkSize) : this(
|
||||
chunkPosition.leftTopTile(chunkSize),
|
||||
chunkPosition.rightBottomTile(chunkSize),
|
||||
constructor(chunkPosition: MapChunkPosition) : this(
|
||||
chunkPosition.leftTopTile(),
|
||||
chunkPosition.rightBottomTile(),
|
||||
chunkPosition.chunkSize,
|
||||
)
|
||||
|
||||
override val start: MapTilePosition
|
||||
|
|
@ -88,117 +176,6 @@ data class MapChunkPositionBounds(
|
|||
}
|
||||
|
||||
|
||||
/**
|
||||
* 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,
|
||||
) : Comparable<MapTilePosition> {
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
override operator fun compareTo(other: MapTilePosition): Int =
|
||||
when {
|
||||
other.x != x -> x.compareTo(other.x)
|
||||
else -> y.compareTo(other.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)
|
||||
|
||||
operator fun MapTilePosition.plus(addend: MapTilePosition) =
|
||||
MapTilePosition(x + addend.x, y + addend.y)
|
||||
|
||||
|
||||
operator fun MapTilePosition.minus(subtrahend: MapTilePosition) =
|
||||
MapTilePosition(x - subtrahend.x, y - subtrahend.y)
|
||||
|
||||
|
||||
fun MapTilePosition.toMapChunkPosition(chunkSize: ChunkSize) =
|
||||
MapChunkPosition(
|
||||
floor(x.toDouble() / chunkSize.lengthInTiles.toDouble()).toInt(),
|
||||
floor(y.toDouble() / chunkSize.lengthInTiles.toDouble()).toInt(),
|
||||
)
|
||||
|
||||
|
||||
fun MapEntityPosition.toMapTilePosition() =
|
||||
MapChunkPosition(floor(x).toInt(), floor(y).toInt())
|
||||
|
||||
fun MapEntityPosition.toMapChunkPosition(chunkSize: ChunkSize) =
|
||||
MapChunkPosition(
|
||||
floor(x / chunkSize.lengthInTiles.toDouble()).toInt(),
|
||||
floor(y / chunkSize.lengthInTiles.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: ChunkSize): MapTilePosition =
|
||||
MapTilePosition(
|
||||
x * chunkSize.lengthInTiles,
|
||||
y * chunkSize.lengthInTiles,
|
||||
)
|
||||
|
||||
fun MapChunkPosition.rightBottomTile(chunkSize: ChunkSize): MapTilePosition =
|
||||
leftTopTile(chunkSize) + (chunkSize.lengthInTiles - 1)
|
||||
|
||||
|
||||
fun MapChunkPosition.iterateTiles(chunkSize: ChunkSize): Iterator<MapTilePosition> {
|
||||
val leftTop = leftTopTile(chunkSize)
|
||||
val rightBottom = rightBottomTile(chunkSize)
|
||||
return iterator {
|
||||
(leftTop.x..rightBottom.x).forEach { x ->
|
||||
(leftTop.y..rightBottom.y).forEach { y ->
|
||||
yield(MapTilePosition(x, y))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//val MapChunkPosition.rightBottomTile: MapTilePosition
|
||||
// get() = leftTopTile + (MAP_CHUNK_SIZE - 1)
|
||||
|
||||
//
|
||||
//val MapChunkPosition.boundingBox: MapBoundingBox
|
||||
// get() = MapBoundingBox(leftTopTile, rightBottomTile)
|
||||
|
||||
|
||||
@Serializable
|
||||
enum class ChunkSize(
|
||||
val zoomLevel: Int,
|
||||
|
|
@ -231,3 +208,17 @@ enum class ChunkSize(
|
|||
val STANDARD: ChunkSize = entries.first { it.zoomLevel == 0 }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Serializable
|
||||
@SerialName("kafkatorio.position.MapBoundingBox")
|
||||
data class MapBoundingBox(
|
||||
val leftTop: MapEntityPosition,
|
||||
val rightBottom: MapEntityPosition,
|
||||
) {
|
||||
val width: Double get() = abs(leftTop.x - rightBottom.x)
|
||||
val height: Double get() = abs(leftTop.y - rightBottom.y)
|
||||
|
||||
val tileWidth: Int get() = ceil(width).toInt()
|
||||
val tileHeight: Int get() = ceil(height).toInt()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -55,6 +55,15 @@ value class PrototypeId private constructor(
|
|||
}
|
||||
|
||||
|
||||
/** Serialize a [PrototypeId] as an integer - more efficient storage */
|
||||
@JvmInline
|
||||
@Serializable
|
||||
value class PrototypeHashCode private constructor(val code: Int) {
|
||||
constructor(id: PrototypeId) : this(id.hashCode())
|
||||
constructor(tile: FactorioPrototype) : this(tile.protoId)
|
||||
}
|
||||
|
||||
|
||||
//@Suppress("unused")
|
||||
//enum class PrototypeTypeId(
|
||||
// val id: String
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
package dev.adamko.kafkatorio.schema.packets
|
||||
|
||||
import dev.adamko.kafkatorio.schema.common.ServerMapChunkId
|
||||
import dev.adamko.kafkatorio.schema.common.TilePngFilename
|
||||
import dev.adamko.kafkatorio.schema.common.ServerMapTilePngFilename
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
|
|
@ -21,7 +21,7 @@ sealed class EventServerPacket {
|
|||
@SerialName("kafkatorio.event-server-packet.ChunkTileSaved")
|
||||
data class ChunkTileSaved(
|
||||
val chunkId: ServerMapChunkId,
|
||||
val filename: TilePngFilename,
|
||||
val filename: ServerMapTilePngFilename,
|
||||
) : EventServerPacket()
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,13 +16,12 @@ data class KafkatorioPacket(
|
|||
|
||||
@Serializable
|
||||
@SerialName("kafkatorio.packet.KafkatorioPacketData")
|
||||
sealed class KafkatorioPacketData {
|
||||
sealed class KafkatorioPacketData
|
||||
|
||||
@Serializable
|
||||
@SerialName("kafkatorio.packet.KafkatorioPacketData.Error")
|
||||
data class Error(
|
||||
val message: String? = null,
|
||||
val rawValue: String? = null,
|
||||
) : KafkatorioPacketData()
|
||||
|
||||
}
|
||||
@Serializable
|
||||
@SerialName("kafkatorio.packet.KafkatorioPacketData.Error")
|
||||
data class KafkatorioPacketDataError(
|
||||
val message: String? = null,
|
||||
val rawValue: String? = null,
|
||||
) : KafkatorioPacketData()
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
package dev.adamko.kafkatorio.schema.packets
|
||||
|
||||
import dev.adamko.kafkatorio.library.LuaJsonList
|
||||
import dev.adamko.kafkatorio.schema.common.FactorioPrototype
|
||||
import dev.adamko.kafkatorio.schema.common.PlayerIndex
|
||||
import dev.adamko.kafkatorio.schema.common.SurfaceIndex
|
||||
|
|
@ -20,7 +21,8 @@ sealed class KafkatorioInstantPacketData : KafkatorioPacketData()
|
|||
@SerialName("kafkatorio.packet.instant.ConfigurationUpdate")
|
||||
data class ConfigurationUpdate(
|
||||
val factorioData: ConfigurationUpdateGameData,
|
||||
val allMods: List<ConfigurationUpdateModData>,
|
||||
// @Contextual
|
||||
val allMods: LuaJsonList<ConfigurationUpdateModData>,
|
||||
val modStartupSettingsChange: Boolean,
|
||||
val migrationApplied: Boolean,
|
||||
) : KafkatorioInstantPacketData() {
|
||||
|
|
@ -62,7 +64,8 @@ data class ConsoleCommandUpdate(
|
|||
@Serializable
|
||||
@SerialName("kafkatorio.packet.instant.PrototypesUpdate")
|
||||
data class PrototypesUpdate(
|
||||
val prototypes: List<FactorioPrototype>,
|
||||
// @Contextual
|
||||
val prototypes: LuaJsonList<FactorioPrototype>,
|
||||
) : KafkatorioInstantPacketData()
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -1,10 +1,12 @@
|
|||
package dev.adamko.kafkatorio.schema.packets
|
||||
|
||||
import dev.adamko.kafkatorio.library.LuaJsonList
|
||||
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.EventName
|
||||
import dev.adamko.kafkatorio.schema.common.FactorioEntityData
|
||||
import dev.adamko.kafkatorio.schema.common.FactorioEntityUpdateDictionary
|
||||
import dev.adamko.kafkatorio.schema.common.ForceIndex
|
||||
import dev.adamko.kafkatorio.schema.common.MapChunkPosition
|
||||
import dev.adamko.kafkatorio.schema.common.MapEntityPosition
|
||||
|
|
@ -26,7 +28,7 @@ sealed class KafkatorioKeyedPacketData : KafkatorioPacketData() {
|
|||
/** A list of all events that have been aggregated into this packet. */
|
||||
// Maybe change to be Map<EventName, Tick>, where Tick is the most recent event?
|
||||
// A complete event timeline isn't necessary. The most recent tick of an event is good enough, and requires less space.
|
||||
abstract val events: Map<EventName, List<Tick>>?
|
||||
abstract val events: Map<EventName, LuaJsonList<Tick>>?
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -52,13 +54,13 @@ data class EntityUpdateKey(
|
|||
data class EntityUpdate(
|
||||
override val key: EntityUpdateKey,
|
||||
|
||||
override val events: Map<EventName, List<Tick>>? = null,
|
||||
override val events: Map<EventName, LuaJsonList<Tick>>? = null,
|
||||
|
||||
val chunkPosition: MapEntityPosition? = null,
|
||||
val graphicsVariation: UByte? = null,
|
||||
val health: Float? = null,
|
||||
val isActive: Boolean? = null,
|
||||
val isRotatable: Boolean? = null,
|
||||
val isActive: Boolean = false,
|
||||
val isRotatable: Boolean = false,
|
||||
val lastUser: UInt? = null,
|
||||
val localisedDescription: String? = null,
|
||||
val localisedName: String? = null,
|
||||
|
|
@ -82,14 +84,14 @@ data class MapChunkTileUpdateKey(
|
|||
data class MapChunkTileUpdate(
|
||||
override val key: MapChunkTileUpdateKey,
|
||||
|
||||
override val events: Map<EventName, List<Tick>>? = null,
|
||||
override val events: Map<EventName, LuaJsonList<Tick>>? = 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,
|
||||
val isDeleted: Boolean = false,
|
||||
) : KafkatorioKeyedPacketData()
|
||||
|
||||
|
||||
|
|
@ -108,7 +110,7 @@ data class PlayerUpdateKey(
|
|||
data class PlayerUpdate(
|
||||
override val key: PlayerUpdateKey,
|
||||
|
||||
override val events: Map<EventName, List<Tick>>? = null,
|
||||
override val events: Map<EventName, LuaJsonList<Tick>>? = null,
|
||||
|
||||
val characterUnitNumber: UnitNumber? = null,
|
||||
val chatColour: Colour? = null,
|
||||
|
|
@ -118,10 +120,10 @@ data class PlayerUpdate(
|
|||
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 isAdmin: Boolean = false,
|
||||
val isConnected: Boolean = false,
|
||||
val isShowOnMap: Boolean = false,
|
||||
val isSpectator: Boolean = false,
|
||||
val lastOnline: Tick? = null,
|
||||
val onlineTime: Tick? = null,
|
||||
val position: MapEntityPosition? = null,
|
||||
|
|
@ -149,20 +151,58 @@ data class MapChunkEntityUpdateKey(
|
|||
) : KafkatorioKeyedPacketKey()
|
||||
|
||||
|
||||
/**
|
||||
* @property[distinctEntities] Distinct entities. The key is arbitrary and is only used
|
||||
* for de-duplicating updates. It should only contain data that can be obtained from
|
||||
* [the value][FactorioEntityData].
|
||||
*/
|
||||
@Serializable
|
||||
@SerialName("kafkatorio.packet.keyed.MapChunkEntityUpdate")
|
||||
data class MapChunkEntityUpdate(
|
||||
override val key: MapChunkEntityUpdateKey,
|
||||
|
||||
override val events: Map<EventName, List<Tick>>? = null,
|
||||
override val events: Map<EventName, LuaJsonList<Tick>>? = null,
|
||||
|
||||
val distinctEntities: Map<String, FactorioEntityData>? = null,
|
||||
) : KafkatorioKeyedPacketData()
|
||||
val entitiesXY: FactorioEntityUpdateDictionary.Entity,
|
||||
) : KafkatorioKeyedPacketData() {
|
||||
|
||||
fun entities(): List<FactorioEntityData.Standard> =
|
||||
entitiesXY.map { (position, entity) ->
|
||||
FactorioEntityData.Standard(
|
||||
protoId = key.protoId,
|
||||
position = position,
|
||||
element = entity,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** */
|
||||
|
||||
|
||||
@Serializable
|
||||
@SerialName("kafkatorio.packet.keyed.MapChunkResourceUpdateKey")
|
||||
data class MapChunkResourceUpdateKey(
|
||||
val protoId: PrototypeId,
|
||||
val surfaceIndex: SurfaceIndex,
|
||||
val chunkPosition: MapChunkPosition,
|
||||
) : KafkatorioKeyedPacketKey()
|
||||
|
||||
|
||||
@Serializable
|
||||
@SerialName("kafkatorio.packet.keyed.MapChunkResourceUpdate")
|
||||
data class MapChunkResourceUpdate(
|
||||
override val key: MapChunkResourceUpdateKey,
|
||||
|
||||
override val events: Map<EventName, LuaJsonList<Tick>>? = null,
|
||||
|
||||
val resourcesXY: FactorioEntityUpdateDictionary.Resource,
|
||||
) : KafkatorioKeyedPacketData() {
|
||||
|
||||
fun resources(): List<FactorioEntityData.Resource> =
|
||||
resourcesXY.map { (position, resource) ->
|
||||
FactorioEntityData.Resource(
|
||||
protoId = key.protoId,
|
||||
position = position,
|
||||
element = resource,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** */
|
||||
|
|
|
|||
|
|
@ -1,10 +1,6 @@
|
|||
package dev.adamko.kafkatorio.events.schema
|
||||
package dev.adamko.kafkatorio.schema.common
|
||||
|
||||
import dev.adamko.kafkatorio.library.jsonMapperKafkatorio
|
||||
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 io.kotest.assertions.withClue
|
||||
import io.kotest.core.spec.style.BehaviorSpec
|
||||
import io.kotest.matchers.floats.shouldBeWithinPercentageOf
|
||||
|
|
@ -2,10 +2,12 @@ package dev.adamko.kafkatorio.schema.common
|
|||
|
||||
import dev.adamko.kafkatorio.library.kxsBinary
|
||||
import io.kotest.assertions.asClue
|
||||
import io.kotest.assertions.withClue
|
||||
import io.kotest.core.spec.style.FunSpec
|
||||
import io.kotest.matchers.collections.shouldContainExactly
|
||||
import io.kotest.matchers.ints.shouldBeExactly
|
||||
import io.kotest.matchers.maps.shouldContainExactly
|
||||
import io.kotest.matchers.shouldBe
|
||||
import kotlinx.serialization.decodeFromByteArray
|
||||
import kotlinx.serialization.encodeToByteArray
|
||||
|
||||
|
|
@ -17,18 +19,17 @@ class MapChunkPositionBoundsTest : FunSpec({
|
|||
|
||||
context("chunk size 32") {
|
||||
|
||||
val chunkPos = MapChunkPosition(0, 0)
|
||||
val bounds = MapChunkPositionBounds(chunkPos, ChunkSize.CHUNK_032)
|
||||
val chunkPos = MapChunkPosition(0, 0, ChunkSize.CHUNK_032)
|
||||
|
||||
test("leftTop should be 0,0") {
|
||||
bounds.leftTop.asClue {
|
||||
chunkPos.bounds.leftTop.asClue {
|
||||
it.x shouldBeExactly 0
|
||||
it.y shouldBeExactly 0
|
||||
}
|
||||
}
|
||||
|
||||
test("rightBottom should be 31,31") {
|
||||
bounds.rightBottom.asClue {
|
||||
chunkPos.bounds.rightBottom.asClue {
|
||||
it.x shouldBeExactly 31
|
||||
it.y shouldBeExactly 31
|
||||
}
|
||||
|
|
@ -38,18 +39,17 @@ class MapChunkPositionBoundsTest : FunSpec({
|
|||
|
||||
context("chunk size 256") {
|
||||
|
||||
val chunkPos = MapChunkPosition(0, 0)
|
||||
val bounds = MapChunkPositionBounds(chunkPos, ChunkSize.CHUNK_256)
|
||||
val chunkPos = MapChunkPosition(0, 0, ChunkSize.CHUNK_256)
|
||||
|
||||
test("leftTop should be 0,0") {
|
||||
bounds.leftTop.asClue {
|
||||
chunkPos.bounds.leftTop.asClue {
|
||||
it.x shouldBeExactly 0
|
||||
it.y shouldBeExactly 0
|
||||
}
|
||||
}
|
||||
|
||||
test("rightBottom should be 255,255") {
|
||||
bounds.rightBottom.asClue {
|
||||
chunkPos.bounds.rightBottom.asClue {
|
||||
it.x shouldBeExactly 255
|
||||
it.y shouldBeExactly 255
|
||||
}
|
||||
|
|
@ -61,18 +61,17 @@ class MapChunkPositionBoundsTest : FunSpec({
|
|||
|
||||
context("chunk size 32") {
|
||||
|
||||
val chunkPos = MapChunkPosition(-10, -10)
|
||||
val bounds = MapChunkPositionBounds(chunkPos, ChunkSize.CHUNK_032)
|
||||
val chunkPos = MapChunkPosition(-10, -10, ChunkSize.CHUNK_032)
|
||||
|
||||
test("leftTop should be -320,-320") {
|
||||
bounds.leftTop.asClue {
|
||||
chunkPos.bounds.leftTop.asClue {
|
||||
it.x shouldBeExactly -320
|
||||
it.y shouldBeExactly -320
|
||||
}
|
||||
}
|
||||
|
||||
test("rightBottom should be -289,-289") {
|
||||
bounds.rightBottom.asClue {
|
||||
chunkPos.bounds.rightBottom.asClue {
|
||||
it.x shouldBeExactly -289
|
||||
it.y shouldBeExactly -289
|
||||
}
|
||||
|
|
@ -81,18 +80,17 @@ class MapChunkPositionBoundsTest : FunSpec({
|
|||
|
||||
context("chunk size 256") {
|
||||
|
||||
val chunkPos = MapChunkPosition(-10, -10)
|
||||
val bounds = MapChunkPositionBounds(chunkPos, ChunkSize.CHUNK_256)
|
||||
val chunkPos = MapChunkPosition(-10, -10, ChunkSize.CHUNK_256)
|
||||
|
||||
test("leftTop should be -2560") {
|
||||
bounds.leftTop.asClue {
|
||||
chunkPos.bounds.leftTop.asClue {
|
||||
it.x shouldBeExactly -2560
|
||||
it.y shouldBeExactly -2560
|
||||
}
|
||||
}
|
||||
|
||||
test("rightBottom should be (-2560 + 255)") {
|
||||
bounds.rightBottom.asClue {
|
||||
chunkPos.bounds.rightBottom.asClue {
|
||||
it.x shouldBeExactly (-2560 + 255)
|
||||
it.y shouldBeExactly (-2560 + 255)
|
||||
}
|
||||
|
|
@ -134,4 +132,57 @@ class MapChunkPositionBoundsTest : FunSpec({
|
|||
decoded.toMapTileList() shouldContainExactly actual.toMapTileList()
|
||||
}
|
||||
}
|
||||
|
||||
context("verify map chunk 'contains'") {
|
||||
|
||||
context("MapChunkPosition(1, 1)") {
|
||||
|
||||
val chunkPos = MapChunkPosition(1, 1, ChunkSize.CHUNK_032) // from 32,32 to 63,63
|
||||
|
||||
test("tile outside of chunk") {
|
||||
val outerTilePos = MapTilePosition(1, 1)
|
||||
|
||||
withClue("$outerTilePos in ${chunkPos.bounds}") {
|
||||
(outerTilePos in chunkPos) shouldBe false
|
||||
}
|
||||
}
|
||||
|
||||
test("tile inside of chunk") {
|
||||
val innerTilePos = MapTilePosition(34, 33)
|
||||
|
||||
withClue("$innerTilePos in ${chunkPos.bounds}") {
|
||||
(innerTilePos in chunkPos) shouldBe true
|
||||
}
|
||||
}
|
||||
|
||||
test("entity outside of chunk") {
|
||||
val outerEntityPos = MapEntityPosition(1.0, 1.0)
|
||||
|
||||
withClue("$outerEntityPos in ${chunkPos.bounds}") {
|
||||
(outerEntityPos !in chunkPos) shouldBe true
|
||||
(outerEntityPos in chunkPos) shouldBe false
|
||||
}
|
||||
}
|
||||
|
||||
test("entity inside of chunk") {
|
||||
val innerEntityPos = MapEntityPosition(34.0, 33.0)
|
||||
|
||||
withClue("$innerEntityPos in ${chunkPos.bounds}") {
|
||||
(innerEntityPos !in chunkPos) shouldBe false
|
||||
(innerEntityPos in chunkPos) shouldBe true
|
||||
}
|
||||
}
|
||||
}
|
||||
context(" MapChunkPosition(9, -5)") {
|
||||
val chunkPos2 = MapChunkPosition(9, -5, ChunkSize.CHUNK_032)
|
||||
|
||||
test("tile inside of chunk") {
|
||||
val innerTilePos = MapTilePosition(288, -160)
|
||||
|
||||
withClue("$innerTilePos in ${chunkPos2.bounds}") {
|
||||
(innerTilePos in chunkPos2) shouldBe true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ class MapChunkPositionTest : FunSpec({
|
|||
|
||||
context("MapChunkPosition(0, 0)") {
|
||||
|
||||
val chunkPos = MapChunkPosition(0, 0)
|
||||
val chunkPos = MapChunkPosition(0, 0, ChunkSize.CHUNK_032)
|
||||
|
||||
test("should contain tiles from (0,0) to (31,31)") {
|
||||
checkAll(mapTilePositionArb(0..31)) { tilePos ->
|
||||
|
|
@ -40,7 +40,7 @@ class MapChunkPositionTest : FunSpec({
|
|||
}
|
||||
context("MapChunkPosition(-10, -10)") {
|
||||
|
||||
val chunkPos = MapChunkPosition(-10, -10)
|
||||
val chunkPos = MapChunkPosition(-10, -10, ChunkSize.CHUNK_032)
|
||||
|
||||
test("should contain tiles from (-320,-320) to (-289,-289)") {
|
||||
checkAll(mapTilePositionArb(-320..-289)) { tilePos ->
|
||||
|
|
|
|||
|
|
@ -1,5 +1,7 @@
|
|||
package dev.adamko.kafkatorio.schema.common
|
||||
|
||||
import dev.adamko.kafkatorio.library.minus
|
||||
import dev.adamko.kafkatorio.library.plus
|
||||
import dev.adamko.kafkatorio.schema.common.MapChunkPositionTest.Companion.mapTilePositionArb
|
||||
import io.kotest.core.spec.style.FunSpec
|
||||
import io.kotest.property.forAll
|
||||
|
|
@ -17,15 +19,27 @@ class MapTilePositionTest : FunSpec({
|
|||
}
|
||||
}
|
||||
|
||||
test("less than or equal to <=") {
|
||||
test("equal comparing to ==") {
|
||||
forAll(mapTilePositionArb()) { tilePos ->
|
||||
tilePos <= (tilePos)
|
||||
tilePos.compareTo(tilePos) == 0
|
||||
}
|
||||
}
|
||||
|
||||
test("less than or equal to <=") {
|
||||
forAll(
|
||||
mapTilePositionArb(),
|
||||
mapTilePositionArb(0..1),
|
||||
) { tilePos, delta ->
|
||||
tilePos <= (tilePos + delta)
|
||||
}
|
||||
}
|
||||
|
||||
test("greater than or equal to >=") {
|
||||
forAll(mapTilePositionArb()) { tilePos ->
|
||||
tilePos >= (tilePos)
|
||||
forAll(
|
||||
mapTilePositionArb(),
|
||||
mapTilePositionArb(0..1),
|
||||
) { tilePos, delta ->
|
||||
tilePos >= (tilePos - delta)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -37,7 +51,5 @@ class MapTilePositionTest : FunSpec({
|
|||
tilePos > (tilePos - delta)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
})
|
||||
|
|
|
|||
|
|
@ -1,9 +1,6 @@
|
|||
package dev.adamko.kafkatorio.events.schema
|
||||
package dev.adamko.kafkatorio.schema.common
|
||||
|
||||
import dev.adamko.kafkatorio.library.jsonMapperKafkatorio
|
||||
import dev.adamko.kafkatorio.schema.common.Colour
|
||||
import dev.adamko.kafkatorio.schema.common.FactorioPrototype
|
||||
import dev.adamko.kafkatorio.schema.common.PrototypeId
|
||||
import dev.adamko.kafkatorio.schema.packets.KafkatorioPacket
|
||||
import dev.adamko.kafkatorio.schema.packets.PrototypesUpdate
|
||||
import io.kotest.core.spec.style.BehaviorSpec
|
||||
|
|
@ -1,22 +1,21 @@
|
|||
package dev.adamko.kafkatorio.events.schema
|
||||
package dev.adamko.kafkatorio.schema.packets
|
||||
|
||||
import dev.adamko.kafkatorio.library.LuaJsonList
|
||||
import dev.adamko.kafkatorio.library.jsonMapperKafkatorio
|
||||
import dev.adamko.kafkatorio.schema.common.ChunkSize
|
||||
import dev.adamko.kafkatorio.schema.common.EventName
|
||||
import dev.adamko.kafkatorio.schema.common.MapChunkPosition
|
||||
import dev.adamko.kafkatorio.schema.common.MapTileDictionary
|
||||
import dev.adamko.kafkatorio.schema.common.PrototypeId
|
||||
import dev.adamko.kafkatorio.schema.common.SurfaceIndex
|
||||
import dev.adamko.kafkatorio.schema.common.tick
|
||||
import dev.adamko.kafkatorio.schema.packets.KafkatorioPacket
|
||||
import dev.adamko.kafkatorio.schema.packets.MapChunkTileUpdate
|
||||
import dev.adamko.kafkatorio.schema.packets.MapChunkTileUpdateKey
|
||||
import io.kotest.core.spec.style.FunSpec
|
||||
import io.kotest.matchers.shouldBe
|
||||
import kotlinx.serialization.decodeFromString
|
||||
|
||||
class MapChunkUpdateTest : FunSpec({
|
||||
class MapChunkTileUpdateTest : FunSpec({
|
||||
|
||||
context("MapChunkUpdate update") {
|
||||
context("MapChunkTileUpdate update") {
|
||||
//language=json
|
||||
val json = """
|
||||
{
|
||||
|
|
@ -26,7 +25,8 @@ class MapChunkUpdateTest : FunSpec({
|
|||
"surfaceIndex": 1,
|
||||
"chunkPosition": [
|
||||
-6,
|
||||
-3
|
||||
-3,
|
||||
"CHUNK_032"
|
||||
]
|
||||
},
|
||||
"tileDictionary": {
|
||||
|
|
@ -61,11 +61,11 @@ class MapChunkUpdateTest : FunSpec({
|
|||
modVersion = "0.4.0",
|
||||
data = MapChunkTileUpdate(
|
||||
key = MapChunkTileUpdateKey(
|
||||
MapChunkPosition(-6, -3),
|
||||
MapChunkPosition(-6, -3, ChunkSize.CHUNK_032),
|
||||
SurfaceIndex(1u),
|
||||
),
|
||||
events = mapOf(
|
||||
EventName("on_chunk_generated") to listOf(1u.tick)
|
||||
EventName("on_chunk_generated") to LuaJsonList(listOf(1u.tick))
|
||||
),
|
||||
tileDictionary = MapTileDictionary(
|
||||
tilesXY = mapOf(
|
||||
|
|
@ -1,5 +1,6 @@
|
|||
package dev.adamko.kafkatorio.events.schema
|
||||
package dev.adamko.kafkatorio.schema.packets
|
||||
|
||||
import dev.adamko.kafkatorio.library.LuaJsonList
|
||||
import dev.adamko.kafkatorio.library.jsonMapperKafkatorio
|
||||
import dev.adamko.kafkatorio.schema.common.Colour
|
||||
import dev.adamko.kafkatorio.schema.common.EntityIdentifiersData
|
||||
|
|
@ -12,15 +13,12 @@ import dev.adamko.kafkatorio.schema.common.SurfaceIndex
|
|||
import dev.adamko.kafkatorio.schema.common.Tick
|
||||
import dev.adamko.kafkatorio.schema.common.UnitNumber
|
||||
import dev.adamko.kafkatorio.schema.common.tick
|
||||
import dev.adamko.kafkatorio.schema.packets.KafkatorioPacket
|
||||
import dev.adamko.kafkatorio.schema.packets.PlayerUpdate
|
||||
import dev.adamko.kafkatorio.schema.packets.PlayerUpdateKey
|
||||
import dev.adamko.kafkatorio.schema.packets.SurfaceUpdate
|
||||
import io.kotest.core.spec.style.FunSpec
|
||||
import io.kotest.matchers.shouldBe
|
||||
import kotlinx.serialization.decodeFromString
|
||||
import kotlinx.serialization.encodeToString
|
||||
|
||||
|
||||
class UpdatePacketsTest : FunSpec({
|
||||
|
||||
context("player update packet") {
|
||||
|
|
@ -72,8 +70,8 @@ class UpdatePacketsTest : FunSpec({
|
|||
diedCause = null,
|
||||
disconnectReason = null,
|
||||
events = mapOf(
|
||||
EventName("on_player_changed_position") to listOf(7u.tick, 99u.tick),
|
||||
EventName("on_player_joined_game") to listOf(14u.tick),
|
||||
EventName("on_player_changed_position") to LuaJsonList(listOf(7u.tick, 99u.tick)),
|
||||
EventName("on_player_joined_game") to LuaJsonList(listOf(14u.tick)),
|
||||
),
|
||||
forceIndex = ForceIndex(1u),
|
||||
isAdmin = true,
|
||||
|
|
@ -194,7 +192,7 @@ class UpdatePacketsTest : FunSpec({
|
|||
),
|
||||
disconnectReason = null,
|
||||
events = mapOf(
|
||||
EventName("on_player_died") to listOf(2u.tick),
|
||||
EventName("on_player_died") to LuaJsonList(listOf(2u.tick)),
|
||||
),
|
||||
forceIndex = null,
|
||||
key = PlayerUpdateKey(PlayerIndex(1u)),
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package dev.adamko.kafkatorio.events.schema
|
||||
package dev.adamko.kafkatorio.schema
|
||||
|
||||
import dev.adamko.kafkatorio.schema.common.PlayerIndex
|
||||
import dev.adamko.kafkatorio.schema.common.SurfaceIndex
|
||||
|
|
@ -85,13 +85,21 @@ class MySourceCodeGenerator(
|
|||
config: KxsTsConfig,
|
||||
) : TsSourceCodeGenerator.Default(config) {
|
||||
|
||||
private val seenTypeAliases: MutableSet<TsElementId> = mutableSetOf()
|
||||
|
||||
override fun generateTypeAlias(element: TsTypeAlias): String {
|
||||
|
||||
if (!seenTypeAliases.add(element.id)) {
|
||||
return ""
|
||||
}
|
||||
|
||||
return if (element.id.name in listOf("PlayerIndex", "SurfaceIndex", "UnitNumber")) {
|
||||
""
|
||||
} else if (
|
||||
(element.typeRef as? TsTypeRef.Literal)?.element is TsLiteral.Custom
|
||||
|| element.id.name == "Tick"
|
||||
|| element.id.name == "FactorioEntityUpdateEntityDictionary"
|
||||
|| element.id.name == "FactorioEntityUpdateResourceDictionary"
|
||||
) {
|
||||
val aliasedRef = generateTypeReference(element.typeRef)
|
||||
"""
|
||||
|
|
@ -1,11 +1,12 @@
|
|||
package dev.adamko.kafkatorio.library
|
||||
|
||||
import dev.adamko.kafkatorio.schema.common.ServerMapChunkId
|
||||
import dev.adamko.kafkatorio.schema.common.ChunkSize
|
||||
import dev.adamko.kafkatorio.schema.common.ColourHex
|
||||
import dev.adamko.kafkatorio.schema.common.FactorioServerId
|
||||
import dev.adamko.kafkatorio.schema.common.MapChunkPosition
|
||||
import dev.adamko.kafkatorio.schema.common.MapTilePosition
|
||||
import dev.adamko.kafkatorio.schema.common.ServerMapChunkId
|
||||
import dev.adamko.kafkatorio.schema.common.ServerMapTileLayer
|
||||
import dev.adamko.kafkatorio.schema.common.SurfaceIndex
|
||||
import io.kotest.assertions.withClue
|
||||
import io.kotest.core.spec.style.FunSpec
|
||||
|
|
@ -51,7 +52,7 @@ class KxsBinaryTests : FunSpec({
|
|||
|
||||
test("byte array") {
|
||||
val ba = byteArrayOf(
|
||||
4, 116, 101, 115, 116, 2, 0, 0, 0, 1, 0, 0, 0, 2, 0, 0, 0, 1, 0, 0, 0, 3
|
||||
4, 116, 101, 115, 116, 0, 0, 0, 0, 3, 0, 0, 0, 1, 0, 0, 0, 2, 0, 0, 0, 3, 0, 0, 0, 1
|
||||
)
|
||||
|
||||
val result = kxsBinary.decodeFromByteArray(ServerMapChunkId.serializer(), ba)
|
||||
|
|
@ -60,9 +61,9 @@ class KxsBinaryTests : FunSpec({
|
|||
|
||||
result shouldBeEqualToComparingFields ServerMapChunkId(
|
||||
serverId = FactorioServerId("test"),
|
||||
chunkPosition = MapChunkPosition(1, 2),
|
||||
layer = ServerMapTileLayer.TERRAIN,
|
||||
chunkPosition = MapChunkPosition(1, 2, ChunkSize.CHUNK_064),
|
||||
surfaceIndex = SurfaceIndex(1u),
|
||||
chunkSize = ChunkSize.CHUNK_064,
|
||||
)
|
||||
|
||||
}
|
||||
|
|
@ -72,15 +73,15 @@ class KxsBinaryTests : FunSpec({
|
|||
val ba = kxsBinary.encodeToByteArray(
|
||||
ServerMapChunkId(
|
||||
serverId = FactorioServerId("test"),
|
||||
chunkPosition = MapChunkPosition(1, 2),
|
||||
layer = ServerMapTileLayer.TERRAIN,
|
||||
chunkPosition = MapChunkPosition(1, 2, ChunkSize.CHUNK_064),
|
||||
surfaceIndex = SurfaceIndex(1u),
|
||||
chunkSize = ChunkSize.CHUNK_064,
|
||||
)
|
||||
)
|
||||
|
||||
withClue(ba.toString(Charsets.UTF_8)) {
|
||||
ba shouldBe byteArrayOf(
|
||||
4, 116, 101, 115, 116, 2, 0, 0, 0, 1, 0, 0, 0, 2, 0, 0, 0, 1, 0, 0, 0, 3
|
||||
4, 116, 101, 115, 116, 0, 0, 0, 0, 3, 0, 0, 0, 1, 0, 0, 0, 2, 0, 0, 0, 3, 0, 0, 0, 1
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -112,10 +113,10 @@ class KxsBinaryTests : FunSpec({
|
|||
context("ServerMapChunkTiles") {
|
||||
val initial = ServerMapChunkTiles(
|
||||
ServerMapChunkId(
|
||||
FactorioServerId("server-id"),
|
||||
MapChunkPosition(33, 44),
|
||||
SurfaceIndex(4u),
|
||||
ChunkSize.CHUNK_032,
|
||||
serverId = FactorioServerId("server-id"),
|
||||
layer = ServerMapTileLayer.TERRAIN,
|
||||
chunkPosition = MapChunkPosition(33, 44, ChunkSize.CHUNK_032),
|
||||
surfaceIndex = SurfaceIndex(4u),
|
||||
),
|
||||
mapOf(
|
||||
MapTilePosition(1, 2) to ColourHex(1u, 2u, 3u, 4u),
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue