update web-map to include event-update changes

This commit is contained in:
Adam 2022-02-28 20:34:56 +01:00
parent ebb8482201
commit 52fcfbaa93
3 changed files with 159 additions and 63 deletions

View file

@ -1,7 +1,10 @@
package dev.adamko.kafkatorio.webmap 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.Maps
import io.kvision.maps.externals.leaflet.geo.LatLng 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)

View file

@ -1,12 +1,17 @@
package dev.adamko.kafkatorio.webmap package dev.adamko.kafkatorio.webmap
import dev.adamko.kafkatorio.events.schema.FactorioEvent import dev.adamko.kafkatorio.events.schema.Colour
import dev.adamko.kafkatorio.events.schema.PlayerData 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 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
import io.kvision.maps.Maps.Companion.L 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.ReduxStore
import io.kvision.redux.createReduxStore import io.kvision.redux.createReduxStore
import io.kvision.utils.obj import io.kvision.utils.obj
@ -25,71 +30,166 @@ fun createFactorioReduxStore(): ReduxStore<FactorioGameState, FactorioUpdate> =
data class FactorioGameState( data class FactorioGameState(
val kvMaps: Maps, val kvMaps: Maps,
val players: Map<String, PlayerState>, val players: Map<PlayerIndex, PlayerState>,
) )
sealed class FactorioUpdate : RAction { sealed class FactorioUpdate : RAction {
data class PlayerUpdate(val event: FactorioEvent, val playerData: PlayerData) : data class Player(val tick: Tick, val data: PlayerUpdate) : FactorioUpdate()
FactorioUpdate()
} }
fun factorioGameReducer(state: FactorioGameState, update: FactorioUpdate): FactorioGameState = fun factorioGameReducer(state: FactorioGameState, update: FactorioUpdate): FactorioGameState =
when (update) { when (update) {
is FactorioUpdate.PlayerUpdate -> { is FactorioUpdate.Player -> {
val playerState = state.players.getOrElse(update.playerData.name) { val playerState = state.players.getOrElse(update.data.index) {
val newState = PlayerState(update.playerData) PlayerState(update.data.index, update.tick)
state.kvMaps.leafletMap {
addLayer(newState.mapMarker)
newState.mapMarker.openPopup()
// setView(update.playerData.latLng())
}
newState
} }
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) state.copy(players = updatedPlayersState)
} }
} }
data class PlayerState( class PlayerState(
private var playerData: PlayerData, val index: PlayerIndex,
val mapMarker: CircleMarker = createMapMarker(playerData) private var lastTick: Tick,
) { ) {
var playerDetails: PlayerDetailsState = PlayerDetailsState.Unknown(index)
private set
fun update(playerData: PlayerData) { var mapMarker: CircleMarker? = null
this.playerData = playerData private set
mapMarker.setLatLng(playerData.latLng())
println("updated player ${playerData.name}") 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() return when {
val playerMapTooltip = "${playerData.name} [$playerLatLng]" 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) { else ->
color = playerData.colour.toHexString(false) PlayerDetailsState.Unknown(
fillColor = color index = playerDetails.index,
fillOpacity = 1f position = position,
radius = 10 surfaceIndex = surfaceIndex,
className = playerData.objectName.name colour = colour,
// interactive = false name = name,
} )
circle.bindTooltip(playerMapTooltip, obj<Tooltip.TooltipOptions> {
// interactive = false
})
return circle
} }
} }
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<Tooltip.TooltipOptions> {
// 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()
}
} }

View file

@ -1,16 +1,13 @@
package dev.adamko.kafkatorio.webmap package dev.adamko.kafkatorio.webmap
import dev.adamko.kafkatorio.events.schema.ConsoleChatMessage import dev.adamko.kafkatorio.events.schema.EntityUpdate
import dev.adamko.kafkatorio.events.schema.EntityData
import dev.adamko.kafkatorio.events.schema.FactorioConfigurationUpdate import dev.adamko.kafkatorio.events.schema.FactorioConfigurationUpdate
import dev.adamko.kafkatorio.events.schema.FactorioEvent import dev.adamko.kafkatorio.events.schema.FactorioEvent
import dev.adamko.kafkatorio.events.schema.FactorioEventUpdatePacket import dev.adamko.kafkatorio.events.schema.FactorioEventUpdatePacket
import dev.adamko.kafkatorio.events.schema.FactorioPrototypes import dev.adamko.kafkatorio.events.schema.FactorioPrototypes
import dev.adamko.kafkatorio.events.schema.KafkatorioPacket import dev.adamko.kafkatorio.events.schema.KafkatorioPacket
import dev.adamko.kafkatorio.events.schema.MapChunk import dev.adamko.kafkatorio.events.schema.MapChunkUpdate
import dev.adamko.kafkatorio.events.schema.MapTiles import dev.adamko.kafkatorio.events.schema.PlayerUpdate
import dev.adamko.kafkatorio.events.schema.PlayerData
import dev.adamko.kafkatorio.events.schema.SurfaceData
import io.kvision.redux.ReduxStore import io.kvision.redux.ReduxStore
import kotlinx.serialization.decodeFromString import kotlinx.serialization.decodeFromString
import org.w3c.dom.MessageEvent import org.w3c.dom.MessageEvent
@ -39,22 +36,18 @@ class WebsocketService(
println(data.replace('\n', ' ')) println(data.replace('\n', ' '))
when (val event: KafkatorioPacket = jsonMapper.decodeFromString(data)) { when (val event: KafkatorioPacket = jsonMapper.decodeFromString(data)) {
is FactorioEvent -> { is FactorioEvent -> {
when (val eventData = event.data) { }
is PlayerData -> is FactorioEventUpdatePacket -> {
reduxStore.dispatch(FactorioUpdate.PlayerUpdate(event, eventData)) when (val eventUpdate = event.update) {
is ConsoleChatMessage, is PlayerUpdate -> reduxStore.dispatch(FactorioUpdate.Player(event.tick, eventUpdate))
is EntityData, is EntityUpdate,
is MapChunk, is MapChunkUpdate -> {
// is MapTile,
is SurfaceData,
is MapTiles -> {
} }
} }
} }
is FactorioEventUpdatePacket,
is FactorioConfigurationUpdate, is FactorioConfigurationUpdate,
is FactorioPrototypes -> { is FactorioPrototypes -> {
// to be continued... // to be continued...
} }
} }