initial hacky implementation of multi-server support in the frontend
This commit is contained in:
parent
60d61752b3
commit
8a6c49a74b
14 changed files with 452 additions and 161 deletions
|
|
@ -1,9 +1,10 @@
|
|||
package dev.adamko.kafkatorio.webmap
|
||||
|
||||
import dev.adamko.kafkatorio.schema.packets.EventServerPacket
|
||||
import dev.adamko.kafkatorio.webmap.state.FactorioGameState
|
||||
import dev.adamko.kafkatorio.webmap.state.FactorioUpdate
|
||||
import dev.adamko.kafkatorio.webmap.state.createFactorioReduxStore
|
||||
import dev.adamko.kafkatorio.webmap.layout.headerNav
|
||||
import dev.adamko.kafkatorio.webmap.layout.homePage
|
||||
import dev.adamko.kafkatorio.webmap.layout.serverPage
|
||||
import dev.adamko.kafkatorio.webmap.services.WebsocketService
|
||||
import io.kvision.Application
|
||||
import io.kvision.BootstrapCssModule
|
||||
import io.kvision.BootstrapIconsModule
|
||||
|
|
@ -12,17 +13,21 @@ import io.kvision.ChartModule
|
|||
import io.kvision.CoreModule
|
||||
import io.kvision.FontAwesomeModule
|
||||
import io.kvision.html.div
|
||||
import io.kvision.maps.Maps
|
||||
import io.kvision.maps.externals.leaflet.layer.tile.GridLayer
|
||||
import io.kvision.html.header
|
||||
import io.kvision.html.main
|
||||
import io.kvision.module
|
||||
import io.kvision.panel.root
|
||||
import io.kvision.redux.ReduxStore
|
||||
import io.kvision.redux.createReduxStore
|
||||
import io.kvision.require
|
||||
import io.kvision.routing.Routing
|
||||
import io.kvision.startApplication
|
||||
import kotlin.coroutines.CoroutineContext
|
||||
import io.kvision.state.bind
|
||||
import kotlinx.coroutines.CoroutineName
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.SupervisorJob
|
||||
import kotlinx.coroutines.cancel
|
||||
import kotlinx.coroutines.cancelChildren
|
||||
import kotlinx.coroutines.flow.filterIsInstance
|
||||
|
|
@ -33,54 +38,78 @@ import kotlinx.coroutines.flow.onEach
|
|||
val rootJob = Job()
|
||||
|
||||
|
||||
class App(
|
||||
reduxStore: ReduxStore<FactorioGameState, FactorioUpdate>,
|
||||
wsService: WebsocketService,
|
||||
) : Application(), CoroutineScope {
|
||||
class App : Application() {
|
||||
|
||||
override val coroutineContext: CoroutineContext = CoroutineName("WebMapApp")
|
||||
private val coroutineScope: CoroutineScope = CoroutineScope(
|
||||
Dispatchers.Default
|
||||
+ SupervisorJob(rootJob)
|
||||
+ CoroutineName("WebMapApp")
|
||||
)
|
||||
|
||||
private var appState: MutableMap<String, Any> = mutableMapOf()
|
||||
|
||||
var reduxStore: ReduxStore<FactorioGameState, FactorioUpdate> by appState
|
||||
var wsService: WebsocketService by appState
|
||||
var reduxStore: ReduxStore<SiteState, SiteAction> by appState
|
||||
private set
|
||||
// var wsService: WebsocketService by appState
|
||||
// private set
|
||||
|
||||
init {
|
||||
this.reduxStore = reduxStore
|
||||
this.wsService = wsService
|
||||
this.reduxStore = createReduxStore(SiteState::plus, SiteState())
|
||||
// this.wsService = wsService
|
||||
require("./css/kafkatorio.css")
|
||||
}
|
||||
|
||||
private val gameState: FactorioGameState
|
||||
get() = reduxStore.getState()
|
||||
|
||||
private val kvMaps: Maps
|
||||
get() = gameState.map.kvMap
|
||||
// private val gameState: FactorioGameState
|
||||
// get() = reduxStore.getState()
|
||||
|
||||
// private val kvMaps: Maps
|
||||
// get() = gameState.map.kvMap
|
||||
|
||||
override fun start(state: Map<String, Any>) {
|
||||
Routing.init(useHash = false)
|
||||
|
||||
this.appState = state.toMutableMap()
|
||||
|
||||
val siteRouting = SiteRouting(reduxStore)
|
||||
val websocketService = WebsocketService(reduxStore)
|
||||
|
||||
siteRouting.init()
|
||||
|
||||
root("kvapp") {
|
||||
|
||||
header().bind(siteRouting.siteStateStore) { state ->
|
||||
headerNav(state)
|
||||
}
|
||||
|
||||
div("Kafkatorio Web Map")
|
||||
add(kvMaps)
|
||||
|
||||
main().bind(siteRouting.siteStateStore) { state ->
|
||||
when (state.view) {
|
||||
SiteView.HOME -> homePage(state)
|
||||
SiteView.SERVER -> serverPage(state)
|
||||
}
|
||||
}
|
||||
|
||||
kvMaps.leafletMap {
|
||||
invalidateSize(true)
|
||||
eachLayer({
|
||||
if (it is GridLayer<*>) {
|
||||
it.redraw()
|
||||
}
|
||||
})
|
||||
// div("Kafkatorio Web Map")
|
||||
// add(kvMaps)
|
||||
}
|
||||
|
||||
wsService.packetsFlow
|
||||
// kvMaps.leafletMap {
|
||||
// invalidateSize(true)
|
||||
// eachLayer({
|
||||
// if (it is GridLayer<*>) {
|
||||
// it.redraw()
|
||||
// }
|
||||
// })
|
||||
// }
|
||||
|
||||
websocketService.packetsFlow
|
||||
.filterIsInstance<EventServerPacket.ChunkTileSaved>()
|
||||
.onEach { tileSavedEvent ->
|
||||
// println("[packetsFlow] triggering tile refresh ${tileSavedEvent.filename.value}")
|
||||
gameState.map.refreshUpdatedTilePng(tileSavedEvent.filename)
|
||||
}.launchIn(this)
|
||||
reduxStore.dispatch(SiteAction.EventServerUpdate(tileSavedEvent))
|
||||
// reduxStore.map.refreshUpdatedTilePng(tileSavedEvent.filename)
|
||||
}.launchIn(coroutineScope)
|
||||
|
||||
}
|
||||
|
||||
|
|
@ -96,14 +125,13 @@ class App(
|
|||
fun main() {
|
||||
startApplication(
|
||||
{
|
||||
val reduxStore = createFactorioReduxStore()
|
||||
val wsService = WebsocketService(
|
||||
// "ws://localhost:9073/ws", // TODO load from config
|
||||
reduxStore = reduxStore,
|
||||
)
|
||||
// val reduxStore = createFactorioReduxStore()
|
||||
// val wsService = WebsocketService(
|
||||
// reduxStore = reduxStore,
|
||||
// )
|
||||
App(
|
||||
reduxStore = reduxStore,
|
||||
wsService = wsService,
|
||||
// reduxStore = reduxStore,
|
||||
// wsService = wsService,
|
||||
)
|
||||
},
|
||||
module.hot,
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
package dev.adamko.kafkatorio.webmap
|
||||
package dev.adamko.kafkatorio.webmap.config
|
||||
|
||||
import dev.adamko.kafkatorio.library.jsonMapperKafkatorio
|
||||
import kotlinx.serialization.json.Json
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
package dev.adamko.kafkatorio.webmap.layout
|
||||
|
||||
import dev.adamko.kafkatorio.webmap.SiteState
|
||||
import dev.adamko.kafkatorio.webmap.SiteView
|
||||
import io.kvision.core.Container
|
||||
import io.kvision.html.div
|
||||
import io.kvision.html.link
|
||||
import io.kvision.html.nav
|
||||
|
||||
fun Container.headerNav(state: SiteState) {
|
||||
nav(className = "navbar") {
|
||||
div(className = "container") {
|
||||
link("home", "#${SiteView.HOME.pathMatcher}")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,30 @@
|
|||
package dev.adamko.kafkatorio.webmap.layout
|
||||
|
||||
import dev.adamko.kafkatorio.webmap.SiteState
|
||||
import io.kvision.core.Container
|
||||
import io.kvision.html.div
|
||||
import io.kvision.html.h5
|
||||
import io.kvision.html.link
|
||||
|
||||
|
||||
fun Container.homePage(state: SiteState) {
|
||||
div(className = "home-page") {
|
||||
servers(state)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private fun Container.servers(state: SiteState) {
|
||||
div(className = "servers") {
|
||||
h5("Servers")
|
||||
if (state.serverIds != null) {
|
||||
state.serverIds.forEach { serverId ->
|
||||
div {
|
||||
link("server", "/servers/$serverId")
|
||||
}
|
||||
}
|
||||
} else {
|
||||
div("no servers available")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
package dev.adamko.kafkatorio.webmap.layout
|
||||
|
||||
import dev.adamko.kafkatorio.webmap.SiteState
|
||||
import io.kvision.core.Container
|
||||
import io.kvision.html.div
|
||||
import io.kvision.maps.externals.leaflet.layer.tile.GridLayer
|
||||
|
||||
|
||||
fun Container.serverPage(state: SiteState) {
|
||||
val factorioGameState = state.factorioGameState
|
||||
div(className = "server-page") {
|
||||
if (factorioGameState != null) {
|
||||
|
||||
add(factorioGameState.map.kvMap)
|
||||
|
||||
factorioGameState.map.kvMap.leafletMap {
|
||||
invalidateSize(true)
|
||||
eachLayer({
|
||||
if (it is GridLayer<*>) {
|
||||
it.redraw()
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,5 +1,6 @@
|
|||
package dev.adamko.kafkatorio.webmap
|
||||
|
||||
import dev.adamko.kafkatorio.schema.common.FactorioServerId
|
||||
import dev.adamko.kafkatorio.schema.common.ServerMapTileLayer
|
||||
import dev.adamko.kafkatorio.schema.common.ServerMapTilePngFilename
|
||||
import dev.adamko.kafkatorio.webmap.externals.TileOnLoadFn
|
||||
|
|
@ -31,7 +32,9 @@ import org.w3c.fetch.RequestInit
|
|||
import org.w3c.fetch.RequestMode
|
||||
|
||||
|
||||
class FactorioMap {
|
||||
class FactorioMap(
|
||||
private val serverId: FactorioServerId,
|
||||
) {
|
||||
|
||||
val kvMap: Maps = Maps {
|
||||
width = 800.px
|
||||
|
|
@ -155,16 +158,18 @@ class FactorioMap {
|
|||
return baseTileLayer
|
||||
}
|
||||
|
||||
private fun tileLayerUrlTemplate(layer: ServerMapTileLayer): String {
|
||||
return "/kafkatorio/data/servers/${serverId.id}/map/layers/${layer.dir}/s1/z{z}/x{x}/y{y}.png"
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val DYNAMIC_RELOAD_ATT = "dynamic-reload"
|
||||
|
||||
private val serverName: String
|
||||
get() = window.location.pathname
|
||||
.substringAfter("kafkatorio/data/servers/")
|
||||
.substringBefore("/")
|
||||
// private val serverName: String?
|
||||
// get() = window.location.pathname
|
||||
// .substringAfter("servers")
|
||||
// .substringBefore("/")
|
||||
// .ifBlank { null }
|
||||
|
||||
private fun tileLayerUrlTemplate(layer: ServerMapTileLayer): String {
|
||||
return "/kafkatorio/data/servers/$serverName/map/layers/${layer.dir}/s1/z{z}/x{x}/y{y}.png"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,161 @@
|
|||
package dev.adamko.kafkatorio.webmap
|
||||
|
||||
import dev.adamko.kafkatorio.schema.common.FactorioServerId
|
||||
import dev.adamko.kafkatorio.schema.common.Tick
|
||||
import dev.adamko.kafkatorio.schema.packets.EventServerPacket
|
||||
import dev.adamko.kafkatorio.schema.packets.PlayerUpdate
|
||||
import dev.adamko.kafkatorio.webmap.services.EventsServerClient
|
||||
import dev.adamko.kafkatorio.webmap.state.FactorioGameState
|
||||
import io.kvision.navigo.Match
|
||||
import io.kvision.navigo.Navigo
|
||||
import io.kvision.redux.RAction
|
||||
import io.kvision.redux.ReduxStore
|
||||
import io.kvision.routing.routing
|
||||
import kotlin.js.RegExp
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.SupervisorJob
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
|
||||
class SiteRouting(
|
||||
val siteStateStore: ReduxStore<SiteState, SiteAction>
|
||||
) : CoroutineScope by CoroutineScope(Dispatchers.Default + SupervisorJob()) {
|
||||
|
||||
fun init() {
|
||||
routing
|
||||
.on(SiteView.HOME) { homepageView() }
|
||||
.on(SiteView.SERVER) { match ->
|
||||
serverView(match)
|
||||
}
|
||||
.resolve()
|
||||
}
|
||||
|
||||
private fun homepageView() {
|
||||
siteStateStore.dispatch(SiteAction.HomePage)
|
||||
launch {
|
||||
val serverIds = EventsServerClient.serverIds()
|
||||
siteStateStore.dispatch(SiteAction.ServerIdsLoaded(serverIds))
|
||||
}
|
||||
}
|
||||
|
||||
private fun serverView(match: Match) {
|
||||
val serverId = match.data[0] as? String ?: error("invalid server id")
|
||||
siteStateStore.dispatch(SiteAction.ServerPage(FactorioServerId(serverId)))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
data class SiteState(
|
||||
val view: SiteView = SiteView.HOME,
|
||||
|
||||
val serverIds: List<FactorioServerId>? = null,
|
||||
|
||||
val factorioGameState: FactorioGameState? = null,
|
||||
) {
|
||||
|
||||
operator fun plus(action: SiteAction): SiteState = when (action) {
|
||||
is SiteAction.HomePage -> copy(view = SiteView.HOME)
|
||||
|
||||
is SiteAction.ServerPage -> copy(
|
||||
view = SiteView.SERVER,
|
||||
factorioGameState = FactorioGameState(action.serverId),
|
||||
)
|
||||
|
||||
is SiteAction.ServerIdsLoaded -> copy(serverIds = action.serverIds)
|
||||
|
||||
is SiteAction.FactorioUpdate -> copy(
|
||||
factorioGameState = factorioGameState?.let { it + action }
|
||||
)
|
||||
|
||||
is SiteAction.EventServerUpdate -> copy(
|
||||
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
sealed interface SiteAction : RAction {
|
||||
object HomePage : SiteAction
|
||||
data class ServerPage(val serverId: FactorioServerId) : SiteAction
|
||||
|
||||
|
||||
data class ServerIdsLoaded(val serverIds: List<FactorioServerId>) : SiteAction
|
||||
|
||||
|
||||
/** Updates from `events-server` */
|
||||
sealed class FactorioUpdate : SiteAction {
|
||||
data class Player(val tick: Tick, val data: PlayerUpdate) : FactorioUpdate()
|
||||
}
|
||||
|
||||
data class EventServerUpdate(val packet: EventServerPacket) : SiteAction
|
||||
}
|
||||
|
||||
|
||||
sealed interface SiteView {
|
||||
val pathMatcher: Any
|
||||
|
||||
sealed interface StaticPath : SiteView {
|
||||
override val pathMatcher: String
|
||||
}
|
||||
|
||||
sealed interface DynamicPath : SiteView {
|
||||
override val pathMatcher: RegExp
|
||||
}
|
||||
|
||||
|
||||
object HOME : StaticPath {
|
||||
override val pathMatcher: String = "/"
|
||||
}
|
||||
|
||||
object SERVER : DynamicPath {
|
||||
override val pathMatcher: RegExp = RegExp("^servers/${FactorioServerId.validIdRegex}")
|
||||
}
|
||||
}
|
||||
|
||||
//val SiteView.urlMatcher: String
|
||||
// get() = when (this) {
|
||||
// SiteView.HOME -> "/"
|
||||
// SiteView.SERVER -> "servers/${FactorioServerId.validIdRegex}"
|
||||
// }
|
||||
|
||||
private fun Navigo.on(view: SiteView, handler: (Match) -> Unit): Navigo {
|
||||
return when (view) {
|
||||
is SiteView.StaticPath -> on(view.pathMatcher, { match: Match -> handler(match) })
|
||||
is SiteView.DynamicPath -> on(view.pathMatcher, { match: Match -> handler(match) })
|
||||
}
|
||||
}
|
||||
|
||||
//private fun Navigo.on(
|
||||
// view: SiteView,
|
||||
// before: BeforeHook? = null,
|
||||
// after: AfterHook? = null,
|
||||
// leave: LeaveHook? = null,
|
||||
// already: AlreadyHook? = null,
|
||||
// handler: (Match) -> Unit,
|
||||
//): Navigo {
|
||||
//
|
||||
// val hooks: RouteHooks? =
|
||||
// if (before != null || after != null || leave != null || already != null) {
|
||||
// jso {
|
||||
// if (before != null) this@jso.before = before
|
||||
// if (after != null) this@jso.after = after
|
||||
// if (leave != null) this@jso.leave = leave
|
||||
// if (already != null) this@jso.already = already
|
||||
// }
|
||||
// } else {
|
||||
// null
|
||||
// }
|
||||
//
|
||||
// return when (view) {
|
||||
// is SiteView.StaticPath -> when (hooks) {
|
||||
// null -> on(view.pathMatcher, { match: Match -> handler(match) })
|
||||
// else -> on(view.pathMatcher, { match: Match -> handler(match) }, hooks)
|
||||
// }
|
||||
//
|
||||
// is SiteView.DynamicPath -> when (hooks) {
|
||||
// null -> on(view.pathMatcher, { match: Match -> handler(match) })
|
||||
// else -> on(view.pathMatcher, { match: Match -> handler(match) }, hooks)
|
||||
// }
|
||||
// }
|
||||
//}
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
package dev.adamko.kafkatorio.webmap.services
|
||||
|
||||
import dev.adamko.kafkatorio.schema.common.FactorioServerId
|
||||
import io.kvision.rest.RestClient
|
||||
import io.kvision.rest.call
|
||||
import kotlinx.browser.window
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.SupervisorJob
|
||||
import kotlinx.coroutines.await
|
||||
|
||||
object EventsServerClient :
|
||||
CoroutineScope by CoroutineScope(Dispatchers.Default + SupervisorJob()) {
|
||||
|
||||
private val restClient = RestClient()
|
||||
|
||||
|
||||
/** Fetch all possible server IDs. */
|
||||
suspend fun serverIds(): List<FactorioServerId> =
|
||||
restClient
|
||||
.call<List<FactorioServerId>>(window.location.origin + "/kafkatorio/data/servers")
|
||||
.await()
|
||||
|
||||
|
||||
}
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package dev.adamko.kafkatorio.webmap
|
||||
package dev.adamko.kafkatorio.webmap.services
|
||||
|
||||
import dev.adamko.kafkatorio.schema.packets.ConfigurationUpdate
|
||||
import dev.adamko.kafkatorio.schema.packets.ConsoleChatUpdate
|
||||
|
|
@ -12,8 +12,11 @@ import dev.adamko.kafkatorio.schema.packets.MapChunkTileUpdate
|
|||
import dev.adamko.kafkatorio.schema.packets.PlayerUpdate
|
||||
import dev.adamko.kafkatorio.schema.packets.PrototypesUpdate
|
||||
import dev.adamko.kafkatorio.schema.packets.SurfaceUpdate
|
||||
import dev.adamko.kafkatorio.webmap.state.FactorioGameState
|
||||
import dev.adamko.kafkatorio.webmap.state.FactorioUpdate
|
||||
import dev.adamko.kafkatorio.webmap.SiteAction
|
||||
import dev.adamko.kafkatorio.webmap.SiteState
|
||||
import dev.adamko.kafkatorio.webmap.config.ApplicationProperties
|
||||
import dev.adamko.kafkatorio.webmap.config.jsonMapper
|
||||
import dev.adamko.kafkatorio.webmap.rootJob
|
||||
import io.kvision.redux.ReduxStore
|
||||
import kotlin.coroutines.CoroutineContext
|
||||
import kotlinx.coroutines.CoroutineName
|
||||
|
|
@ -29,14 +32,13 @@ import org.w3c.dom.events.Event
|
|||
|
||||
|
||||
class WebsocketService(
|
||||
wsUrl: String = Props.websocketServerUrl,
|
||||
private val reduxStore: ReduxStore<FactorioGameState, FactorioUpdate>,
|
||||
private val siteStateStore: ReduxStore<SiteState, SiteAction>
|
||||
) : CoroutineScope {
|
||||
|
||||
override val coroutineContext: CoroutineContext =
|
||||
CoroutineName("WebsocketService") + Job(rootJob)
|
||||
|
||||
private val ws = WebSocket(wsUrl)
|
||||
private val ws = WebSocket(ApplicationProperties.websocketServerUrl)
|
||||
// private val ws = WebSocket("ws://localhost:12080/ws/foo")
|
||||
|
||||
private val _packetsFlow = MutableSharedFlow<EventServerPacket>()
|
||||
|
|
@ -78,7 +80,9 @@ class WebsocketService(
|
|||
is EventServerPacket.Kafkatorio ->
|
||||
when (val packetData = packet.packet.data) {
|
||||
is PlayerUpdate ->
|
||||
reduxStore.dispatch(FactorioUpdate.Player(packet.packet.tick, packetData))
|
||||
siteStateStore.dispatch(
|
||||
SiteAction.FactorioUpdate.Player(packet.packet.tick, packetData)
|
||||
)
|
||||
|
||||
is ConfigurationUpdate,
|
||||
is ConsoleChatUpdate,
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package dev.adamko.kafkatorio.webmap
|
||||
package dev.adamko.kafkatorio.webmap.services
|
||||
|
||||
import dev.adamko.kafkatorio.schema.common.MapEntityPosition
|
||||
import dev.adamko.kafkatorio.schema.packets.PlayerUpdate
|
||||
|
|
@ -0,0 +1,82 @@
|
|||
package dev.adamko.kafkatorio.webmap.state
|
||||
|
||||
|
||||
import dev.adamko.kafkatorio.schema.common.Colour
|
||||
import dev.adamko.kafkatorio.schema.common.MapEntityPosition
|
||||
import dev.adamko.kafkatorio.schema.common.SurfaceIndex
|
||||
import dev.adamko.kafkatorio.schema.common.toHexString
|
||||
import dev.adamko.kafkatorio.schema.packets.PlayerUpdate
|
||||
import dev.adamko.kafkatorio.webmap.services.latLng
|
||||
import io.kvision.maps.externals.leaflet.geo.LatLng
|
||||
|
||||
sealed interface PlayerProperties {
|
||||
|
||||
val position: MapEntityPosition?
|
||||
val surfaceIndex: SurfaceIndex?
|
||||
val colour: Colour?
|
||||
val name: String?
|
||||
val latLng: LatLng?
|
||||
val isConnected: Boolean
|
||||
val isShowOnMap: Boolean
|
||||
|
||||
|
||||
data class Partial(
|
||||
override val position: MapEntityPosition? = null,
|
||||
override val surfaceIndex: SurfaceIndex? = null,
|
||||
override val colour: Colour? = null,
|
||||
override val name: String? = null,
|
||||
override val isConnected: Boolean = false,
|
||||
override val isShowOnMap: Boolean = false,
|
||||
) : PlayerProperties {
|
||||
override val latLng: LatLng? = null
|
||||
}
|
||||
|
||||
|
||||
data class Complete(
|
||||
override val position: MapEntityPosition,
|
||||
override val surfaceIndex: SurfaceIndex,
|
||||
override val colour: Colour,
|
||||
override val name: String,
|
||||
override val isConnected: Boolean = false,
|
||||
override val isShowOnMap: Boolean = false,
|
||||
) : PlayerProperties {
|
||||
override val latLng: LatLng = position.latLng()
|
||||
val hexColour: String = colour.toHexString(false)
|
||||
}
|
||||
|
||||
|
||||
fun update(update: PlayerUpdate): PlayerProperties {
|
||||
|
||||
val position = update.position ?: this.position
|
||||
val surfaceIndex = update.surfaceIndex ?: this.surfaceIndex
|
||||
val colour = update.colour ?: this.colour
|
||||
val name = update.name ?: this.name
|
||||
val isConnected = update.isConnected
|
||||
val isShowOnMap = update.isShowOnMap
|
||||
|
||||
return when {
|
||||
position != null
|
||||
&& surfaceIndex != null
|
||||
&& colour != null
|
||||
&& name != null ->
|
||||
Complete(
|
||||
position = position,
|
||||
surfaceIndex = surfaceIndex,
|
||||
colour = colour,
|
||||
name = name,
|
||||
isConnected = isConnected,
|
||||
isShowOnMap = isShowOnMap,
|
||||
)
|
||||
|
||||
else ->
|
||||
Partial(
|
||||
position = position,
|
||||
surfaceIndex = surfaceIndex,
|
||||
colour = colour,
|
||||
name = name,
|
||||
isConnected = isConnected,
|
||||
isShowOnMap = isShowOnMap,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,22 +1,17 @@
|
|||
package dev.adamko.kafkatorio.webmap.state
|
||||
|
||||
import dev.adamko.kafkatorio.schema.common.Colour
|
||||
import dev.adamko.kafkatorio.schema.common.MapEntityPosition
|
||||
import dev.adamko.kafkatorio.schema.common.PlayerIndex
|
||||
import dev.adamko.kafkatorio.schema.common.SurfaceIndex
|
||||
import dev.adamko.kafkatorio.schema.common.Tick
|
||||
import dev.adamko.kafkatorio.schema.common.toHexString
|
||||
import dev.adamko.kafkatorio.schema.packets.PlayerUpdate
|
||||
import dev.adamko.kafkatorio.webmap.latLng
|
||||
import dev.adamko.kafkatorio.webmap.SiteAction
|
||||
import io.kvision.maps.Maps
|
||||
import io.kvision.maps.externals.leaflet.geo.LatLng
|
||||
import io.kvision.maps.externals.leaflet.layer.vector.CircleMarker
|
||||
import kotlinx.js.jso
|
||||
|
||||
|
||||
fun handlePlayerUpdate(
|
||||
gameState: FactorioGameState,
|
||||
update: FactorioUpdate.Player,
|
||||
update: SiteAction.FactorioUpdate.Player,
|
||||
): FactorioGameState {
|
||||
|
||||
val previous = gameState.players[update.data.key.index]
|
||||
|
|
@ -105,76 +100,3 @@ data class PlayerState(
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
sealed interface PlayerProperties {
|
||||
|
||||
val position: MapEntityPosition?
|
||||
val surfaceIndex: SurfaceIndex?
|
||||
val colour: Colour?
|
||||
val name: String?
|
||||
val latLng: LatLng?
|
||||
val isConnected: Boolean
|
||||
val isShowOnMap: Boolean
|
||||
|
||||
|
||||
data class Partial(
|
||||
override val position: MapEntityPosition? = null,
|
||||
override val surfaceIndex: SurfaceIndex? = null,
|
||||
override val colour: Colour? = null,
|
||||
override val name: String? = null,
|
||||
override val isConnected: Boolean = false,
|
||||
override val isShowOnMap: Boolean = false,
|
||||
) : PlayerProperties {
|
||||
override val latLng: LatLng? = null
|
||||
}
|
||||
|
||||
|
||||
data class Complete(
|
||||
override val position: MapEntityPosition,
|
||||
override val surfaceIndex: SurfaceIndex,
|
||||
override val colour: Colour,
|
||||
override val name: String,
|
||||
override val isConnected: Boolean = false,
|
||||
override val isShowOnMap: Boolean = false,
|
||||
) : PlayerProperties {
|
||||
override val latLng: LatLng = position.latLng()
|
||||
val hexColour: String = colour.toHexString(false)
|
||||
}
|
||||
|
||||
|
||||
fun update(update: PlayerUpdate): PlayerProperties {
|
||||
|
||||
val position = update.position ?: this.position
|
||||
val surfaceIndex = update.surfaceIndex ?: this.surfaceIndex
|
||||
val colour = update.colour ?: this.colour
|
||||
val name = update.name ?: this.name
|
||||
val isConnected = update.isConnected
|
||||
val isShowOnMap = update.isShowOnMap
|
||||
|
||||
return when {
|
||||
position != null
|
||||
&& surfaceIndex != null
|
||||
&& colour != null
|
||||
&& name != null ->
|
||||
Complete(
|
||||
position = position,
|
||||
surfaceIndex = surfaceIndex,
|
||||
colour = colour,
|
||||
name = name,
|
||||
isConnected = isConnected,
|
||||
isShowOnMap = isShowOnMap,
|
||||
)
|
||||
|
||||
else ->
|
||||
Partial(
|
||||
position = position,
|
||||
surfaceIndex = surfaceIndex,
|
||||
colour = colour,
|
||||
name = name,
|
||||
isConnected = isConnected,
|
||||
isShowOnMap = isShowOnMap,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,41 +1,33 @@
|
|||
package dev.adamko.kafkatorio.webmap.state
|
||||
|
||||
import dev.adamko.kafkatorio.schema.common.FactorioServerId
|
||||
import dev.adamko.kafkatorio.schema.common.PlayerIndex
|
||||
import dev.adamko.kafkatorio.schema.common.Tick
|
||||
import dev.adamko.kafkatorio.schema.packets.PlayerUpdate
|
||||
import dev.adamko.kafkatorio.webmap.FactorioMap
|
||||
import dev.adamko.kafkatorio.webmap.SiteAction
|
||||
import io.kvision.maps.externals.leaflet.layer.LayerGroup
|
||||
import io.kvision.redux.ReduxStore
|
||||
import io.kvision.redux.createReduxStore
|
||||
import redux.RAction
|
||||
|
||||
|
||||
fun createFactorioReduxStore(): ReduxStore<FactorioGameState, FactorioUpdate> =
|
||||
createReduxStore(
|
||||
::factorioGameReducer,
|
||||
FactorioGameState(
|
||||
map = FactorioMap(),
|
||||
players = emptyMap(),
|
||||
)
|
||||
)
|
||||
//fun createFactorioReduxStore(): ReduxStore<FactorioGameState, FactorioUpdate> =
|
||||
// createReduxStore(
|
||||
// ::factorioGameReducer,
|
||||
// FactorioGameState(
|
||||
// map = FactorioMap(),
|
||||
// players = emptyMap(),
|
||||
// )
|
||||
// )
|
||||
|
||||
|
||||
data class FactorioGameState(
|
||||
val map: FactorioMap,
|
||||
val players: Map<PlayerIndex, PlayerState>,
|
||||
val serverId: FactorioServerId,
|
||||
val map: FactorioMap = FactorioMap(serverId),
|
||||
val players: Map<PlayerIndex, PlayerState> = emptyMap(),
|
||||
) {
|
||||
|
||||
val playerIconsLayer: LayerGroup
|
||||
get() = map.playerIconsLayer
|
||||
}
|
||||
|
||||
|
||||
sealed class FactorioUpdate : RAction {
|
||||
data class Player(val tick: Tick, val data: PlayerUpdate) : FactorioUpdate()
|
||||
}
|
||||
|
||||
|
||||
fun factorioGameReducer(gameState: FactorioGameState, update: FactorioUpdate): FactorioGameState =
|
||||
when (update) {
|
||||
is FactorioUpdate.Player -> handlePlayerUpdate(gameState, update)
|
||||
operator fun plus(action: SiteAction.FactorioUpdate): FactorioGameState =
|
||||
when (action) {
|
||||
is SiteAction.FactorioUpdate.Player -> handlePlayerUpdate(this, action)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue