From 52fcfbaa939bdda6f7a2c43241e1c2b90396df4e Mon Sep 17 00:00:00 2001 From: Adam <897017+aSemy@users.noreply.github.com> Date: Mon, 28 Feb 2022 20:34:56 +0100 Subject: [PATCH] update web-map to include event-update changes --- .../adamko/kafkatorio/webmap/converters.kt | 7 +- .../dev/adamko/kafkatorio/webmap/state.kt | 186 ++++++++++++++---- .../adamko/kafkatorio/webmap/websockets.kt | 29 ++- 3 files changed, 159 insertions(+), 63 deletions(-) diff --git a/modules/web-map/src/main/kotlin/dev/adamko/kafkatorio/webmap/converters.kt b/modules/web-map/src/main/kotlin/dev/adamko/kafkatorio/webmap/converters.kt index 386370b..4c241ae 100644 --- a/modules/web-map/src/main/kotlin/dev/adamko/kafkatorio/webmap/converters.kt +++ b/modules/web-map/src/main/kotlin/dev/adamko/kafkatorio/webmap/converters.kt @@ -1,7 +1,10 @@ package dev.adamko.kafkatorio.webmap -import dev.adamko.kafkatorio.events.schema.PlayerData +import dev.adamko.kafkatorio.events.schema.MapEntityPosition +import dev.adamko.kafkatorio.events.schema.PlayerUpdate import io.kvision.maps.Maps import io.kvision.maps.externals.leaflet.geo.LatLng -fun PlayerData.latLng(): LatLng = Maps.L.latLng((position.y * -1) to position.x) +fun PlayerUpdate.latLng(): LatLng? = position?.latLng() + +fun MapEntityPosition.latLng(): LatLng = Maps.L.latLng((this.y * -1) to this.x) diff --git a/modules/web-map/src/main/kotlin/dev/adamko/kafkatorio/webmap/state.kt b/modules/web-map/src/main/kotlin/dev/adamko/kafkatorio/webmap/state.kt index d42f6e9..041eff0 100644 --- a/modules/web-map/src/main/kotlin/dev/adamko/kafkatorio/webmap/state.kt +++ b/modules/web-map/src/main/kotlin/dev/adamko/kafkatorio/webmap/state.kt @@ -1,12 +1,17 @@ package dev.adamko.kafkatorio.webmap -import dev.adamko.kafkatorio.events.schema.FactorioEvent -import dev.adamko.kafkatorio.events.schema.PlayerData +import dev.adamko.kafkatorio.events.schema.Colour +import dev.adamko.kafkatorio.events.schema.MapEntityPosition +import dev.adamko.kafkatorio.events.schema.PlayerIndex +import dev.adamko.kafkatorio.events.schema.PlayerUpdate +import dev.adamko.kafkatorio.events.schema.SurfaceIndex +import dev.adamko.kafkatorio.events.schema.Tick import dev.adamko.kafkatorio.events.schema.converters.toHexString -import io.kvision.maps.externals.leaflet.layer.overlay.Tooltip -import io.kvision.maps.externals.leaflet.layer.vector.CircleMarker import io.kvision.maps.Maps import io.kvision.maps.Maps.Companion.L +import io.kvision.maps.externals.leaflet.geo.LatLng +import io.kvision.maps.externals.leaflet.layer.overlay.Tooltip +import io.kvision.maps.externals.leaflet.layer.vector.CircleMarker import io.kvision.redux.ReduxStore import io.kvision.redux.createReduxStore import io.kvision.utils.obj @@ -25,71 +30,166 @@ fun createFactorioReduxStore(): ReduxStore = data class FactorioGameState( val kvMaps: Maps, - val players: Map, + val players: Map, ) sealed class FactorioUpdate : RAction { - data class PlayerUpdate(val event: FactorioEvent, val playerData: PlayerData) : - FactorioUpdate() + data class Player(val tick: Tick, val data: PlayerUpdate) : FactorioUpdate() } fun factorioGameReducer(state: FactorioGameState, update: FactorioUpdate): FactorioGameState = when (update) { - is FactorioUpdate.PlayerUpdate -> { + is FactorioUpdate.Player -> { - 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 + val playerState = state.players.getOrElse(update.data.index) { + PlayerState(update.data.index, update.tick) } - playerState.update(update.playerData) + playerState.update(update.tick, update.data) - val updatedPlayersState = state.players + (update.playerData.name to playerState) + state.kvMaps.leafletMap { + + val playerMarker = playerState.mapMarker + + if (playerMarker != null) { + when (playerState.playerDetails) { + is PlayerDetailsState.Known -> if (!hasLayer(playerMarker)) addLayer(playerMarker) + is PlayerDetailsState.Unknown -> if (hasLayer(playerMarker)) removeLayer(playerMarker) + } + } + } + + val updatedPlayersState = state.players + (playerState.index to playerState) state.copy(players = updatedPlayersState) } } -data class PlayerState( - private var playerData: PlayerData, - val mapMarker: CircleMarker = createMapMarker(playerData) +class PlayerState( + val index: PlayerIndex, + private var lastTick: Tick, ) { + var playerDetails: PlayerDetailsState = PlayerDetailsState.Unknown(index) + private set - fun update(playerData: PlayerData) { - this.playerData = playerData - mapMarker.setLatLng(playerData.latLng()) - println("updated player ${playerData.name}") + var mapMarker: CircleMarker? = null + private set + + fun update(tick: Tick, update: PlayerUpdate) { + + if (lastTick.value > tick.value) { + println("discarding old PlayerUpdate, ${lastTick.value} > ${tick.value}") + return + } + this.lastTick = tick + this.playerDetails = updatePlayerDetails(update) + + updateMapPosition() + + println("updated PlayerState index:${playerDetails.index}, name:${playerDetails.name}") } - companion object { + private fun updatePlayerDetails(update: PlayerUpdate): PlayerDetailsState { - fun createMapMarker(playerData: PlayerData): CircleMarker { + val position = update.position ?: playerDetails.position + val surfaceIndex = update.surfaceIndex ?: playerDetails.surfaceIndex + val colour = update.colour ?: playerDetails.colour + val name = update.name ?: playerDetails.name - val playerLatLng = playerData.latLng() - val playerMapTooltip = "${playerData.name} [$playerLatLng]" + return when { + position != null && surfaceIndex != null && colour != null && name != null -> + PlayerDetailsState.Known( + index = playerDetails.index, + position = position, + surfaceIndex = surfaceIndex, + colour = colour, + name = name, + ) - val circle = L.circleMarker(playerLatLng) { - color = playerData.colour.toHexString(false) - fillColor = color - fillOpacity = 1f - radius = 10 - className = playerData.objectName.name -// interactive = false - } - - circle.bindTooltip(playerMapTooltip, obj { -// interactive = false - }) - - return circle + else -> + PlayerDetailsState.Unknown( + index = playerDetails.index, + position = position, + surfaceIndex = surfaceIndex, + colour = colour, + name = name, + ) } } + + private fun updateMapPosition() { + + when (val details = playerDetails) { + is PlayerDetailsState.Unknown -> { + println("removing player ${details.index} map marker") + mapMarker?.remove() + } + is PlayerDetailsState.Known -> updateMapMarker(details) + } + + } + + private fun updateMapMarker(playerDetails: PlayerDetailsState.Known) { + + if (mapMarker == null) { + mapMarker = L.circleMarker(playerDetails.latLng) { + fillOpacity = 1f + radius = 10 + className = "player-marker" +// interactive = false + }.also { circle -> + circle.bindTooltip("", obj { +// interactive = false + }) + } + } + + mapMarker?.apply { + val playerColour = playerDetails.colour.toHexString(false) + options.color = playerColour + options.fillColor = playerColour + + setTooltipContent("${playerDetails.name} [${playerDetails.latLng}]") + setLatLng(playerDetails.latLng) + } + + } +} + + +sealed interface PlayerDetailsState { + + val index: PlayerIndex + + val position: MapEntityPosition? + val surfaceIndex: SurfaceIndex? + val colour: Colour? + val name: String? + val latLng: LatLng? + + data class Unknown( + override val index: PlayerIndex, + + override val position: MapEntityPosition? = null, + override val surfaceIndex: SurfaceIndex? = null, + override val colour: Colour? = null, + override val name: String? = null, + ) : PlayerDetailsState { + override val latLng: LatLng? = null + } + + data class Known( + override val index: PlayerIndex, + + override val position: MapEntityPosition, + override val surfaceIndex: SurfaceIndex, + override val colour: Colour, + override val name: String, + ) : PlayerDetailsState { + override val latLng: LatLng = position.latLng() + } + } diff --git a/modules/web-map/src/main/kotlin/dev/adamko/kafkatorio/webmap/websockets.kt b/modules/web-map/src/main/kotlin/dev/adamko/kafkatorio/webmap/websockets.kt index 9023676..3e0c8a5 100644 --- a/modules/web-map/src/main/kotlin/dev/adamko/kafkatorio/webmap/websockets.kt +++ b/modules/web-map/src/main/kotlin/dev/adamko/kafkatorio/webmap/websockets.kt @@ -1,16 +1,13 @@ package dev.adamko.kafkatorio.webmap -import dev.adamko.kafkatorio.events.schema.ConsoleChatMessage -import dev.adamko.kafkatorio.events.schema.EntityData +import dev.adamko.kafkatorio.events.schema.EntityUpdate import dev.adamko.kafkatorio.events.schema.FactorioConfigurationUpdate import dev.adamko.kafkatorio.events.schema.FactorioEvent import dev.adamko.kafkatorio.events.schema.FactorioEventUpdatePacket import dev.adamko.kafkatorio.events.schema.FactorioPrototypes import dev.adamko.kafkatorio.events.schema.KafkatorioPacket -import dev.adamko.kafkatorio.events.schema.MapChunk -import dev.adamko.kafkatorio.events.schema.MapTiles -import dev.adamko.kafkatorio.events.schema.PlayerData -import dev.adamko.kafkatorio.events.schema.SurfaceData +import dev.adamko.kafkatorio.events.schema.MapChunkUpdate +import dev.adamko.kafkatorio.events.schema.PlayerUpdate import io.kvision.redux.ReduxStore import kotlinx.serialization.decodeFromString import org.w3c.dom.MessageEvent @@ -39,22 +36,18 @@ class WebsocketService( println(data.replace('\n', ' ')) when (val event: KafkatorioPacket = jsonMapper.decodeFromString(data)) { - is FactorioEvent -> { - when (val eventData = event.data) { - is PlayerData -> - reduxStore.dispatch(FactorioUpdate.PlayerUpdate(event, eventData)) - is ConsoleChatMessage, - is EntityData, - is MapChunk, -// is MapTile, - is SurfaceData, - is MapTiles -> { + is FactorioEvent -> { + } + is FactorioEventUpdatePacket -> { + when (val eventUpdate = event.update) { + is PlayerUpdate -> reduxStore.dispatch(FactorioUpdate.Player(event.tick, eventUpdate)) + is EntityUpdate, + is MapChunkUpdate -> { } } } - is FactorioEventUpdatePacket, is FactorioConfigurationUpdate, - is FactorioPrototypes -> { + is FactorioPrototypes -> { // to be continued... } }