it is literally working

This commit is contained in:
Adam 2022-01-04 15:09:50 +01:00
parent 841f07c847
commit a0c330384a
17 changed files with 499 additions and 63 deletions

View file

@ -42,6 +42,14 @@ is provided.
* https://jbrandhorst.com/post/go-protobuf-tips/ * https://jbrandhorst.com/post/go-protobuf-tips/
##### wasm
kt -> wasm -> lua?
kt-flatbuffers -> wasm -> lua?
https://www.npmjs.com/package/wasm2lua?activeTab=readme
##### flatbuffers ##### flatbuffers
https://github.com/dvidelabs/flatcc https://github.com/dvidelabs/flatcc
@ -56,6 +64,3 @@ https://github.com/dvidelabs/flatcc
https://www.zash.se/lua-cbor.html https://www.zash.se/lua-cbor.html
https://code.zash.se/lua-cbor/archive/68b3a36d0816.tar.gz https://code.zash.se/lua-cbor/archive/68b3a36d0816.tar.gz

View file

@ -2,6 +2,13 @@
# yarn lockfile v1 # yarn lockfile v1
"@babel/runtime@^7.9.2":
version "7.16.5"
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.16.5.tgz#7f3e34bf8bdbbadf03fbb7b1ea0d929569c9487a"
integrity sha512-TXWihFIS3Pyv5hzR7j6ihmeLkZfrXGxAr5UfSl8CHf+6q/wpiYDkUau0czckpYG8QmnCIuPpdLtuA9VmuGGyMA==
dependencies:
regenerator-runtime "^0.13.4"
"@discoveryjs/json-ext@^0.5.0": "@discoveryjs/json-ext@^0.5.0":
version "0.5.6" version "0.5.6"
resolved "https://registry.yarnpkg.com/@discoveryjs/json-ext/-/json-ext-0.5.6.tgz#d5e0706cf8c6acd8c6032f8d54070af261bbbb2f" resolved "https://registry.yarnpkg.com/@discoveryjs/json-ext/-/json-ext-0.5.6.tgz#d5e0706cf8c6acd8c6032f8d54070af261bbbb2f"
@ -81,6 +88,11 @@
resolved "https://registry.yarnpkg.com/@types/estree/-/estree-0.0.50.tgz#1e0caa9364d3fccd2931c3ed96fdbeaa5d4cca83" resolved "https://registry.yarnpkg.com/@types/estree/-/estree-0.0.50.tgz#1e0caa9364d3fccd2931c3ed96fdbeaa5d4cca83"
integrity sha512-C6N5s2ZFtuZRj54k2/zyRhNDjJwwcViAM3Nbm8zjBpbqAdZ00mr0CFxvSKeO8Y/e03WVFLpQMdHYVfUd6SB+Hw== integrity sha512-C6N5s2ZFtuZRj54k2/zyRhNDjJwwcViAM3Nbm8zjBpbqAdZ00mr0CFxvSKeO8Y/e03WVFLpQMdHYVfUd6SB+Hw==
"@types/geojson@*", "@types/geojson@7946.0.8":
version "7946.0.8"
resolved "https://registry.yarnpkg.com/@types/geojson/-/geojson-7946.0.8.tgz#30744afdb385e2945e22f3b033f897f76b1f12ca"
integrity sha512-1rkryxURpr6aWP7R786/UQOkJ3PcpQiWkAXBmdWc7ryFWqN6a4xfK7BtjXvFBKO9LjQ+MWQSWxYeZX1OApnArA==
"@types/glob@5 - 7": "@types/glob@5 - 7":
version "7.2.0" version "7.2.0"
resolved "https://registry.yarnpkg.com/@types/glob/-/glob-7.2.0.tgz#bc1b5bf3aa92f25bd5dd39f35c57361bdce5b2eb" resolved "https://registry.yarnpkg.com/@types/glob/-/glob-7.2.0.tgz#bc1b5bf3aa92f25bd5dd39f35c57361bdce5b2eb"
@ -101,6 +113,13 @@
resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.9.tgz#97edc9037ea0c38585320b28964dde3b39e4660d" resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.9.tgz#97edc9037ea0c38585320b28964dde3b39e4660d"
integrity sha512-qcUXuemtEu+E5wZSJHNxUXeCZhAfXKQ41D+duX+VYPde7xyEVZci+/oXKJL13tnRs9lR2pr4fod59GT6/X1/yQ== integrity sha512-qcUXuemtEu+E5wZSJHNxUXeCZhAfXKQ41D+duX+VYPde7xyEVZci+/oXKJL13tnRs9lR2pr4fod59GT6/X1/yQ==
"@types/leaflet@^1.7.7":
version "1.7.7"
resolved "https://registry.yarnpkg.com/@types/leaflet/-/leaflet-1.7.7.tgz#3c9f629126e03405ed68cc1aa395e32df3312376"
integrity sha512-5/nNqTviSqUwPxbS3PlHl8ODjfnlpOXSFtUAi7dexnisO8E3pfyotChRY/hlIYGvbBiBcKNd6V18xA8O4OpHiQ==
dependencies:
"@types/geojson" "*"
"@types/minimatch@*": "@types/minimatch@*":
version "3.0.5" version "3.0.5"
resolved "https://registry.yarnpkg.com/@types/minimatch/-/minimatch-3.0.5.tgz#1001cc5e6a3704b83c236027e77f2f58ea010f40" resolved "https://registry.yarnpkg.com/@types/minimatch/-/minimatch-3.0.5.tgz#1001cc5e6a3704b83c236027e77f2f58ea010f40"
@ -949,6 +968,14 @@ dukat@0.5.8-rc.3:
google-protobuf "3.12.2" google-protobuf "3.12.2"
typescript "3.9.5" typescript "3.9.5"
dukat@0.5.8-rc.4:
version "0.5.8-rc.4"
resolved "https://registry.yarnpkg.com/dukat/-/dukat-0.5.8-rc.4.tgz#90384dcb50b14c26f0e99dae92b2dea44f5fce21"
integrity sha512-ZnMt6DGBjlVgK2uQamXfd7uP/AxH7RqI0BL9GLrrJb2gKdDxvJChWy+M9AQEaL+7/6TmxzJxFOsRiInY9oGWTA==
dependencies:
google-protobuf "3.12.2"
typescript "3.9.5"
ee-first@1.1.1: ee-first@1.1.1:
version "1.1.1" version "1.1.1"
resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d" resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d"
@ -1280,6 +1307,11 @@ function-bind@^1.1.1:
resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d" resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d"
integrity sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A== integrity sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==
geojson@^0.5.0:
version "0.5.0"
resolved "https://registry.yarnpkg.com/geojson/-/geojson-0.5.0.tgz#3cd6c96399be65b56ee55596116fe9191ce701c0"
integrity sha1-PNbJY5m+ZbVu5VWWEW/pGRznAcA=
get-caller-file@^2.0.5: get-caller-file@^2.0.5:
version "2.0.5" version "2.0.5"
resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e" resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e"
@ -1845,6 +1877,11 @@ kvision-assets@^5.0.7:
resolved "https://registry.yarnpkg.com/kvision-assets/-/kvision-assets-5.0.7.tgz#e6dc3e28a2d244b4c89318b060d3e7e337c15495" resolved "https://registry.yarnpkg.com/kvision-assets/-/kvision-assets-5.0.7.tgz#e6dc3e28a2d244b4c89318b060d3e7e337c15495"
integrity sha512-szoL8ixFRTivLkbnmkWFjGJMk3bsUW/loUS5QfWThPwFnyFEw8ht3eBUrJ0V51Rn/WaJbVoPvY4SfIaiNu13bg== integrity sha512-szoL8ixFRTivLkbnmkWFjGJMk3bsUW/loUS5QfWThPwFnyFEw8ht3eBUrJ0V51Rn/WaJbVoPvY4SfIaiNu13bg==
leaflet@^1.7.1:
version "1.7.1"
resolved "https://registry.yarnpkg.com/leaflet/-/leaflet-1.7.1.tgz#10d684916edfe1bf41d688a3b97127c0322a2a19"
integrity sha512-/xwPEBidtg69Q3HlqPdU3DnrXQOvQU/CCHA1tcDQVzOwm91YMYaILjNp7L4Eaw5Z4sOYdbBz6koWyibppd8Zqw==
loader-runner@^4.2.0: loader-runner@^4.2.0:
version "4.2.0" version "4.2.0"
resolved "https://registry.yarnpkg.com/loader-runner/-/loader-runner-4.2.0.tgz#d7022380d66d14c5fb1d496b89864ebcfd478384" resolved "https://registry.yarnpkg.com/loader-runner/-/loader-runner-4.2.0.tgz#d7022380d66d14c5fb1d496b89864ebcfd478384"
@ -2430,6 +2467,23 @@ rechoir@^0.7.0:
dependencies: dependencies:
resolve "^1.9.0" resolve "^1.9.0"
redux-thunk@^2.4.0:
version "2.4.1"
resolved "https://registry.yarnpkg.com/redux-thunk/-/redux-thunk-2.4.1.tgz#0dd8042cf47868f4b29699941de03c9301a75714"
integrity sha512-OOYGNY5Jy2TWvTL1KgAlVy6dcx3siPJ1wTq741EPyUKfn6W6nChdICjZwCd0p8AZBs5kWpZlbkXW2nE/zjUa+Q==
redux@^4.1.2:
version "4.1.2"
resolved "https://registry.yarnpkg.com/redux/-/redux-4.1.2.tgz#140f35426d99bb4729af760afcf79eaaac407104"
integrity sha512-SH8PglcebESbd/shgf6mii6EIoRM0zrQyjcuQ+ojmfxjTtE0z9Y8pa62iA/OJ58qjP6j27uyW4kUF4jl/jd6sw==
dependencies:
"@babel/runtime" "^7.9.2"
regenerator-runtime@^0.13.4:
version "0.13.9"
resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.9.tgz#8925742a98ffd90814988d7566ad30ca3b263b52"
integrity sha512-p3VT+cOEgxFsRRA9X4lkI1E+k2/CtnKtU4gcxyaCUreilL/vqI6CdZ3wxVUx3UOUg+gnUOQQcRI7BmSI656MYA==
regexp.prototype.flags@^1.2.0: regexp.prototype.flags@^1.2.0:
version "1.3.1" version "1.3.1"
resolved "https://registry.yarnpkg.com/regexp.prototype.flags/-/regexp.prototype.flags-1.3.1.tgz#7ef352ae8d159e758c0eadca6f8fcb4eef07be26" resolved "https://registry.yarnpkg.com/regexp.prototype.flags/-/regexp.prototype.flags-1.3.1.tgz#7ef352ae8d159e758c0eadca6f8fcb4eef07be26"

View file

@ -13,7 +13,8 @@ camel-kafkaConnectors = "0.11.0"
http4k = "4.17.6.0" http4k = "4.17.6.0"
kafka = "3.0.0" kafka = "3.0.0"
kotlinx-serialization = "1.3.2" kotlinx-serialization = "1.3.2"
kvision = "5.6.1" #kvision = "5.6.1"
kvision = "5.6.2-SNAPSHOT"
log4j2 = "2.14.1" log4j2 = "2.14.1"
qosLogback = "1.2.5" qosLogback = "1.2.5"
slf4j = "1.7.30" slf4j = "1.7.30"
@ -50,8 +51,12 @@ kotlinx-serialization-bom = { group = "org.jetbrains.kotlinx", name = "kotlinx-
kotlinx-serialization-core = { group = "org.jetbrains.kotlinx", name = "kotlinx-serialization-core" } kotlinx-serialization-core = { group = "org.jetbrains.kotlinx", name = "kotlinx-serialization-core" }
kotlinx-serialization-json = { group = "org.jetbrains.kotlinx", name = "kotlinx-serialization-json" } kotlinx-serialization-json = { group = "org.jetbrains.kotlinx", name = "kotlinx-serialization-json" }
kotlinx-serialization-yaml = { group = "com.charleskorn.kaml", name = "kaml", version = "0.37.0" } kotlinx-serialization-yaml = { group = "com.charleskorn.kaml", name = "kaml", version = "0.37.0" }
kotlinx-coroutines = { group = "org.jetbrains.kotlinx", name = "kotlinx-coroutines-core", version = "1.6.0" } kotlinx-coroutines = { group = "org.jetbrains.kotlinx", name = "kotlinx-coroutines-core", version = "1.6.0" }
kotlinx-html = { group = "org.jetbrains.kotlinx", name = "kotlinx-html", version = "0.7.3" } kotlinx-html = { group = "org.jetbrains.kotlinx", name = "kotlinx-html", version = "0.7.3" }
kotlinx-nodejs = { group = "org.jetbrains.kotlinx", name = "kotlinx-nodejs", version = "0.0.7" }
## http4k ## ## http4k ##
http4k-bom = { group = "org.http4k", name = "http4k-bom", version.ref = "http4k" } http4k-bom = { group = "org.http4k", name = "http4k-bom", version.ref = "http4k" }

View file

@ -11,6 +11,13 @@ interface FactorioEvent<T extends FactorioObjectData> {
tick: uint; tick: uint;
} }
interface Colour {
alpha: float;
blue: float;
green: float;
red: float;
}
interface PositionData { interface PositionData {
x: double; x: double;
y: double; y: double;
@ -19,6 +26,9 @@ interface PositionData {
interface PlayerData extends FactorioObjectData { interface PlayerData extends FactorioObjectData {
associated_characters_unit_numbers: uint[]; associated_characters_unit_numbers: uint[];
character_unit_number: uint | null; character_unit_number: uint | null;
chat_colour: Colour;
colour: Colour;
last_online: uint;
name: string; name: string;
object_name: string; object_name: string;
position: PositionData; position: PositionData;

View file

@ -10,6 +10,9 @@ export namespace Serdes {
character_unit_number: player.character?.unit_number ?? null, character_unit_number: player.character?.unit_number ?? null,
associated_characters_unit_numbers: charIds, associated_characters_unit_numbers: charIds,
position: positionTableToTable(player.position), position: positionTableToTable(player.position),
colour: mapColour(player.color),
chat_colour: mapColour(player.chat_color),
last_online: player.last_online
} }
} }
@ -62,4 +65,14 @@ export namespace Serdes {
} }
} }
export function mapColour(color: Color): Colour {
color = (color as ColorTable)
return {
red: color.r ?? 0,
green: color.g ?? 0,
blue: color.b ?? 0,
alpha: color.a ?? 0,
}
}
} }

View file

@ -30,9 +30,9 @@ kotlin {
val commonMain by getting { val commonMain by getting {
dependencies { dependencies {
implementation(project.dependencies.enforcedPlatform(libs.kotlin.bom)) implementation(project.dependencies.platform(libs.kotlin.bom))
implementation(project.dependencies.enforcedPlatform(libs.kotlinx.serialization.bom)) implementation(project.dependencies.platform(libs.kotlinx.serialization.bom))
implementation(libs.kotlinx.serialization.core) implementation(libs.kotlinx.serialization.core)
implementation(libs.kotlinx.serialization.json) implementation(libs.kotlinx.serialization.json)
@ -44,7 +44,7 @@ kotlin {
implementation(kotlin("test")) implementation(kotlin("test"))
implementation(project.dependencies.enforcedPlatform(libs.kotest.bom)) implementation(project.dependencies.platform(libs.kotest.bom))
implementation("io.kotest:kotest-assertions-core") implementation("io.kotest:kotest-assertions-core")
implementation("io.kotest:kotest-property") implementation("io.kotest:kotest-property")
implementation("io.kotest:kotest-assertions-json") implementation("io.kotest:kotest-assertions-json")

View file

@ -11,13 +11,18 @@ import kotlinx.serialization.json.JsonClassDiscriminator
@Serializable @Serializable
data class FactorioEvent<out T : FactorioObjectData>( data class FactorioEvent<out T : FactorioObjectData>(
/** the initial Factorio event ({defines.events}) trigger */ /** the initial Factorio event ({defines.events}) trigger */
@SerialName("event_type") val eventType: String, @SerialName("event_type")
val eventType: String,
/** Schema versioning */ /** Schema versioning */
@SerialName("mod_version") val modVersion: String, @SerialName("mod_version")
@SerialName("factorio_version") val factorioVersion: String, val modVersion: String,
@SerialName("factorio_version")
val factorioVersion: String,
/** game time */ /** game time */
@SerialName("tick") val tick: UInt, @SerialName("tick")
@SerialName("data") val data: T val tick: UInt,
@SerialName("data")
val data: T
) )
@ -53,10 +58,20 @@ sealed class FactorioObjectData {
@SerialName("LuaPlayer") @SerialName("LuaPlayer")
data class PlayerData( data class PlayerData(
@Serializable(with = ListAsObjectSerializer::class) @Serializable(with = ListAsObjectSerializer::class)
@SerialName("associated_characters_unit_numbers") val associatedCharactersUnitNumbers: List<UInt>, @SerialName("associated_characters_unit_numbers")
@SerialName("character_unit_number") val characterUnitNumber: UInt?, val associatedCharactersUnitNumbers: List<UInt>,
@SerialName("name") val name: String, @SerialName("character_unit_number")
@SerialName("position") val position: PositionData, val characterUnitNumber: UInt?,
@SerialName("name")
val name: String,
@SerialName("position")
val position: PositionData,
@SerialName("colour")
val colour: Colour,
@SerialName("chat_colour")
val chatColour: Colour,
@SerialName("last_online")
val lastOnline: UInt,
) : FactorioObjectData() { ) : FactorioObjectData() {
@Transient @Transient
override val objectName: String = "LuaPlayer" override val objectName: String = "LuaPlayer"
@ -65,16 +80,25 @@ data class PlayerData(
@Serializable @Serializable
@SerialName("LuaEntity") @SerialName("LuaEntity")
data class EntityData( data class EntityData(
@SerialName("active") val active: Boolean, @SerialName("active")
@SerialName("health") val health: Double?, val active: Boolean,
@SerialName("health_ratio") val healthRatio: Double, @SerialName("health")
@SerialName("name") val name: String, val health: Double?,
@SerialName("position") val position: PositionData, @SerialName("health_ratio")
@SerialName("surface_index") val surfaceIndex: Int, val healthRatio: Double,
@SerialName("type") val type: String, @SerialName("name")
@SerialName("unit_number") val unitNumber: UInt? = null, val name: String,
@SerialName("position")
val position: PositionData,
@SerialName("surface_index")
val surfaceIndex: Int,
@SerialName("type")
val type: String,
@SerialName("unit_number")
val unitNumber: UInt? = null,
@SerialName("player_index") val playerIndex: UInt? = null, @SerialName("player_index")
val playerIndex: UInt? = null,
) : FactorioObjectData() { ) : FactorioObjectData() {
@Transient @Transient
override val objectName: String = "LuaEntity" override val objectName: String = "LuaEntity"
@ -83,9 +107,12 @@ data class EntityData(
@Serializable @Serializable
@SerialName("LuaSurface") @SerialName("LuaSurface")
data class SurfaceData( data class SurfaceData(
@SerialName("daytime") val daytime: Double, @SerialName("daytime")
@SerialName("index") val index: UInt, val daytime: Double,
@SerialName("name") val name: String, @SerialName("index")
val index: UInt,
@SerialName("name")
val name: String,
) : FactorioObjectData() { ) : FactorioObjectData() {
@Transient @Transient
override val objectName: String = "LuaSurface" override val objectName: String = "LuaSurface"
@ -93,6 +120,32 @@ data class SurfaceData(
@Serializable @Serializable
data class PositionData( data class PositionData(
@SerialName("x") val x: Double, val x: Double,
@SerialName("y") val y: Double, val y: Double,
) )
/**
* 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. Color 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,
) {
/** True if any value is greater than 1, so the values are hexadecimal. */
fun isDecimal(): Boolean {
return red > 1f
|| green > 1f
|| blue > 1f
|| alpha > 1f
}
/** True if all values are between `[0..1]`. */
fun isPercentage(): Boolean = !isDecimal()
}

View file

@ -21,7 +21,8 @@ class FactorioEventTest : BehaviorSpec({
"position": { "position": {
"x": -3.5703125, "x": -3.5703125,
"y": 29.75 "y": 29.75
} },
"last_online": 123
}, },
"event_type": "on_player_joined_game", "event_type": "on_player_joined_game",
"mod_version": "0.0.4", "mod_version": "0.0.4",
@ -41,7 +42,10 @@ class FactorioEventTest : BehaviorSpec({
position = PositionData( position = PositionData(
x = -3.5703125, x = -3.5703125,
y = 29.75 y = 29.75
) ),
colour = Colour(),
chatColour = Colour(),
lastOnline = 123u
), ),
eventType = "on_player_joined_game", eventType = "on_player_joined_game",
modVersion = "0.0.4", modVersion = "0.0.4",

View file

@ -1,9 +1,9 @@
import org.jetbrains.kotlin.gradle.plugin.KotlinDependencyHandler
import org.jetbrains.kotlin.gradle.targets.js.webpack.KotlinWebpackConfig import org.jetbrains.kotlin.gradle.targets.js.webpack.KotlinWebpackConfig
plugins { plugins {
id("dev.adamko.kafkatorio.lang.kotlin-js") id("dev.adamko.kafkatorio.lang.kotlin-js")
kotlin("plugin.serialization") kotlin("plugin.serialization")
// kotlin("js") // version kotlinVersion
id("io.kvision") version "5.6.1" id("io.kvision") version "5.6.1"
} }
@ -13,7 +13,7 @@ kotlin {
browser { browser {
runTask { runTask {
outputFileName = "main.bundle.js" outputFileName = "main.bundle.js"
sourceMaps = false sourceMaps = true
devServer = KotlinWebpackConfig.DevServer( devServer = KotlinWebpackConfig.DevServer(
open = false, open = false,
port = 3000, port = 3000,
@ -37,31 +37,54 @@ kotlin {
} }
sourceSets { sourceSets {
val kvisionVersion: String = libs.versions.kvision.get()
all {
languageSettings.optIn("kotlin.RequiresOptIn")
languageSettings.optIn("kotlinx.serialization.ExperimentalSerializationApi")
}
main { main {
dependencies { dependencies {
implementation(projects.modules.eventsSchema)
implementation("org.jetbrains.kotlinx:kotlinx-nodejs:0.0.7") implementation("org.jetbrains.kotlinx:kotlinx-nodejs:0.0.7")
implementation("org.jetbrains.kotlinx:kotlinx-html:0.7.3")
implementation("io.kvision:kvision:$kvisionVersion") kvision("kvision")
implementation("io.kvision:kvision-bootstrap:$kvisionVersion") kvision("kvision-bootstrap")
implementation("io.kvision:kvision-bootstrap-css:$kvisionVersion") kvision("kvision-bootstrap-css")
implementation("io.kvision:kvision-bootstrap-icons:$kvisionVersion") kvision("kvision-bootstrap-icons")
implementation("io.kvision:kvision-fontawesome:$kvisionVersion") kvision("kvision-fontawesome")
implementation("io.kvision:kvision-state:$kvisionVersion") kvision("kvision-state")
implementation("io.kvision:kvision-chart:$kvisionVersion") kvision("kvision-chart")
kvision("kvision-maps")
kvision("kvision-redux")
kvision("kvision-state-flow")
implementation(libs.kotlinx.coroutines)
} }
val webDir = file("src/main/web") val webDir = file("src/main/web")
resources.srcDir(webDir) resources.srcDir(webDir)
} }
test { test {
dependencies { dependencies {
implementation(kotlin("test-js")) implementation(kotlin("test-js"))
implementation("io.kvision:kvision-testutils:$kvisionVersion")
kvision("kvision-testutils")
} }
} }
} }
} }
fun KotlinDependencyHandler.kvision(
module: String,
version: Provider<String> = libs.versions.kvision,
configure: ExternalModuleDependency.() -> Unit = {
isChanging = true
}
) {
implementation("io.kvision:$module:${version.get()}", configure)
}

View file

@ -1,34 +1,73 @@
package dev.adamko.kafkatorio.webmap package dev.adamko.kafkatorio.webmap
import io.kvision.Application import io.kvision.Application
import io.kvision.CoreModule
import io.kvision.BootstrapModule
import io.kvision.BootstrapCssModule import io.kvision.BootstrapCssModule
import io.kvision.BootstrapIconsModule import io.kvision.BootstrapIconsModule
import io.kvision.FontAwesomeModule import io.kvision.BootstrapModule
import io.kvision.ChartModule import io.kvision.ChartModule
import io.kvision.CoreModule
import io.kvision.FontAwesomeModule
import io.kvision.html.div import io.kvision.html.div
import io.kvision.maps.Maps
import io.kvision.module import io.kvision.module
import io.kvision.panel.root import io.kvision.panel.root
import io.kvision.redux.ReduxStore
import io.kvision.startApplication import io.kvision.startApplication
class App : Application() {
override fun start() { class App(
root("kvapp") { reduxStore: ReduxStore<FactorioGameState, FactorioUpdate>,
div("Hello world") wsService: WebsocketService
} ) : Application() {
private var appState: MutableMap<String, Any> = mutableMapOf()
var reduxStore: ReduxStore<FactorioGameState, FactorioUpdate> by appState
var wsService: WebsocketService by appState
init {
this.reduxStore = reduxStore
this.wsService = wsService
}
private val kvMaps: Maps
get() = reduxStore.getState().kvMaps
override fun start(state: Map<String, Any>) {
this.appState = state.toMutableMap()
root("kvapp") {
div("Kafkatorio Web Map")
add(kvMaps)
} }
}
override fun dispose(): Map<String, Any> {
return appState
}
} }
fun main() { fun main() {
startApplication( startApplication(
::App, {
module.hot, val reduxStore = createFactorioReduxStore()
BootstrapModule, val wsService = WebsocketService(
BootstrapCssModule, "ws://localhost:9073/ws",
BootstrapIconsModule, reduxStore,
FontAwesomeModule, )
ChartModule,
CoreModule App(
) reduxStore = reduxStore,
wsService = wsService
)
},
module.hot,
BootstrapModule,
BootstrapCssModule,
BootstrapIconsModule,
FontAwesomeModule,
ChartModule,
CoreModule
)
} }

View file

@ -0,0 +1,36 @@
package dev.adamko.kafkatorio.webmap
import dev.adamko.kafkatorio.events.schema.Colour
import dev.adamko.kafkatorio.events.schema.PlayerData
import externals.leaflet.geo.LatLng
import io.kvision.maps.Maps
import kotlin.math.roundToInt
fun Colour.toHexString(includeAlpha: Boolean = false): String {
val hexConverter: Float.() -> String = if (isDecimal()) {
Float::decimalToHex
} else {
Float::percentageToHex
}
val length = if (includeAlpha) 9 else 7
return buildString(length) {
append('#')
append(red.hexConverter())
append(green.hexConverter())
append(blue.hexConverter())
if (includeAlpha) {
append(alpha.hexConverter())
}
}
}
fun Float.decimalToHex(): String =
roundToInt().toString(16).padStart(2, ' ')
fun Float.percentageToHex(): String =
times(255).roundToInt().toString(16).padStart(2, ' ')
fun PlayerData.latLng(): LatLng = Maps.L.latLng((position.y * -1) to position.x)

View file

@ -0,0 +1,7 @@
package dev.adamko.kafkatorio.webmap
import kotlinx.serialization.json.Json
val jsonMapper = Json {
prettyPrint = true
}

View file

@ -0,0 +1,37 @@
package dev.adamko.kafkatorio.webmap
import externals.leaflet.geo.CRS
import externals.leaflet.geo.LatLng
import io.kvision.maps.DefaultTileLayers
import io.kvision.maps.Maps
import io.kvision.utils.px
//
//val polyline = Maps.L.polyline(
// listOf(
// LatLng(55, 2),
// LatLng(65, 2),
// LatLng(65, 20),
// LatLng(55, 20),
// LatLng(55, 2),
// )
//) {
// noClip = true
//}
fun createFactorioMap() = Maps {
width = 800.px
height = 800.px
margin = 10.px
configureLeafletMap {
setView(LatLng(0, 0), 13)
// DefaultTileLayers.OpenStreetMap.addTo(this)
with(options) {
crs = CRS.Simple
}
}
}

View file

@ -0,0 +1,92 @@
package dev.adamko.kafkatorio.webmap
import dev.adamko.kafkatorio.events.schema.FactorioEvent
import dev.adamko.kafkatorio.events.schema.PlayerData
import externals.leaflet.geo.LatLng
import externals.leaflet.layer.overlay.Tooltip
import externals.leaflet.layer.vector.CircleMarker
import io.kvision.maps.Maps
import io.kvision.maps.Maps.Companion.L
import io.kvision.redux.ReduxStore
import io.kvision.redux.createReduxStore
import io.kvision.utils.obj
import redux.RAction
fun createFactorioReduxStore(): ReduxStore<FactorioGameState, FactorioUpdate> =
createReduxStore(
::factorioGameReducer,
FactorioGameState(
kvMaps = createFactorioMap(),
players = emptyMap()
)
)
data class FactorioGameState(
val kvMaps: Maps,
val players: Map<String, PlayerState>,
)
sealed class FactorioUpdate : RAction {
data class PlayerUpdate(val event: FactorioEvent<*>, val playerData: PlayerData) :
FactorioUpdate()
}
fun factorioGameReducer(state: FactorioGameState, update: FactorioUpdate): FactorioGameState =
when (update) {
is FactorioUpdate.PlayerUpdate -> {
val playerState = state.players.getOrElse(update.playerData.name) {
val newState = PlayerState(update.playerData)
state.kvMaps.leafletMap {
addLayer(newState.mapMarker)
newState.mapMarker.openPopup()
// setView(update.playerData.latLng())
}
newState
}
playerState.update(update.playerData)
val updatedPlayersState = state.players + (update.playerData.name to playerState)
state.copy(players = updatedPlayersState)
}
}
data class PlayerState(
private var playerData: PlayerData,
val mapMarker: CircleMarker = createMapMarker(playerData)
) {
fun update(playerData: PlayerData) {
this.playerData = playerData
mapMarker.setLatLng(playerData.latLng())
println("updated player ${playerData.name}")
}
companion object {
fun createMapMarker(playerData: PlayerData): CircleMarker {
val playerLatLng = playerData.latLng()
val playerMapTooltip = "${playerData.name} [$playerLatLng]"
val circle = L.circleMarker(playerLatLng) {
color = playerData.colour.toHexString(false)
fillColor = color
fillOpacity = 1f
radius = 10
className = playerData.objectName
// interactive = false
}
circle.bindTooltip(playerMapTooltip, obj<Tooltip.TooltipOptions> {
// interactive = false
})
return circle
}
}
}

View file

@ -0,0 +1,46 @@
package dev.adamko.kafkatorio.webmap
import dev.adamko.kafkatorio.events.schema.FactorioEvent
import dev.adamko.kafkatorio.events.schema.FactorioObjectData
import dev.adamko.kafkatorio.events.schema.PlayerData
import io.kvision.redux.ReduxStore
import kotlinx.serialization.decodeFromString
import org.w3c.dom.MessageEvent
import org.w3c.dom.WebSocket
class WebsocketService(
private val wsUrl: String,
private val reduxStore: ReduxStore<FactorioGameState, FactorioUpdate>,
) {
private val ws = WebSocket(wsUrl)
init {
ws.onmessage = ::handleMessageEvent
}
private fun handleMessageEvent(msg: MessageEvent) {
val data = msg.data as? String
if (
data != null
&& data.trim().startsWith("{")
&& data.trim().endsWith("}")
) {
println(data.replace('\n', ' '))
val event: FactorioEvent<FactorioObjectData> = jsonMapper.decodeFromString(data)
when (val eventData = event.data) {
is PlayerData -> {
reduxStore.dispatch(FactorioUpdate.PlayerUpdate(event, eventData))
}
else -> {}
}
} else {
println(data)
}
}
}

View file

@ -1,9 +1,10 @@
<!DOCTYPE html> <!DOCTYPE html>
<html> <html lang="en">
<head> <head>
<meta charset="utf-8"> <meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1"> <meta name="viewport" content="width=device-width, initial-scale=1">
<title>KVision Template</title> <title>Kafkatorio Web Map</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootswatch@5.1.0/dist/superhero/bootstrap.min.css" integrity="sha256-T8h7oW5tHp4MhFhmRoDI8xjnB2ld89qSgc3s9g8T0uU=" crossorigin="anonymous">
<script type="text/javascript" src="main.bundle.js"></script> <script type="text/javascript" src="main.bundle.js"></script>
</head> </head>
<body> <body>

View file

@ -21,3 +21,14 @@ enableFeaturePreview("VERSION_CATALOGS")
enableFeaturePreview("TYPESAFE_PROJECT_ACCESSORS") enableFeaturePreview("TYPESAFE_PROJECT_ACCESSORS")
apply(from = "./buildSrc/repositories.settings.gradle.kts") apply(from = "./buildSrc/repositories.settings.gradle.kts")
@Suppress("UnstableApiUsage") // centralized repositories is incubating
dependencyResolutionManagement {
repositories {
mavenLocal {
content {
includeGroup("io.kvision")
}
}
}
}