Add map management to the server (REST+WS)
This commit is contained in:
parent
3485b8a9fd
commit
03dbd7aad6
62 changed files with 1226 additions and 144 deletions
|
|
@ -6,6 +6,7 @@ import com.pixelized.desktop.lwa.repository.campaign.CampaignRepository
|
|||
import com.pixelized.desktop.lwa.repository.characterSheet.CharacterSheetRepository
|
||||
import com.pixelized.desktop.lwa.repository.inventory.InventoryRepository
|
||||
import com.pixelized.desktop.lwa.repository.item.ItemRepository
|
||||
import com.pixelized.desktop.lwa.repository.map.MapRepository
|
||||
import com.pixelized.desktop.lwa.repository.network.NetworkRepository
|
||||
import com.pixelized.desktop.lwa.repository.settings.SettingsRepository
|
||||
import com.pixelized.desktop.lwa.repository.tag.TagRepository
|
||||
|
|
@ -23,6 +24,7 @@ import kotlinx.coroutines.flow.map
|
|||
import kotlinx.coroutines.flow.onEach
|
||||
|
||||
class DataSyncViewModel(
|
||||
private val mapRepository: MapRepository,
|
||||
private val characterRepository: CharacterSheetRepository,
|
||||
private val inventoryRepository: InventoryRepository,
|
||||
private val alterationRepository: AlterationRepository,
|
||||
|
|
@ -88,6 +90,7 @@ class DataSyncViewModel(
|
|||
campaignRepository.updateCampaign()
|
||||
tagRepository.updateItemTags()
|
||||
itemRepository.updateItemFlow()
|
||||
mapRepository.updateMapFlow()
|
||||
} catch (exception: Exception) {
|
||||
println(exception.message) // TODO proper exception handling
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,7 +12,10 @@ import com.pixelized.desktop.lwa.repository.inventory.InventoryRepository
|
|||
import com.pixelized.desktop.lwa.repository.inventory.InventoryStore
|
||||
import com.pixelized.desktop.lwa.repository.item.ItemRepository
|
||||
import com.pixelized.desktop.lwa.repository.item.ItemStore
|
||||
import com.pixelized.desktop.lwa.repository.map.MapRepository
|
||||
import com.pixelized.desktop.lwa.repository.map.MapStore
|
||||
import com.pixelized.desktop.lwa.repository.network.NetworkRepository
|
||||
import com.pixelized.desktop.lwa.repository.resources.ImageResourcesRepository
|
||||
import com.pixelized.desktop.lwa.repository.roll_history.RollHistoryRepository
|
||||
import com.pixelized.desktop.lwa.repository.settings.SettingsFactory
|
||||
import com.pixelized.desktop.lwa.repository.settings.SettingsRepository
|
||||
|
|
@ -65,6 +68,8 @@ import com.pixelized.desktop.lwa.ui.screen.admin.item.edit.GMItemEditFactory
|
|||
import com.pixelized.desktop.lwa.ui.screen.admin.item.edit.GMItemEditViewModel
|
||||
import com.pixelized.desktop.lwa.ui.screen.admin.item.list.GMItemFactory
|
||||
import com.pixelized.desktop.lwa.ui.screen.admin.item.list.GMItemViewModel
|
||||
import com.pixelized.desktop.lwa.ui.screen.campaign.map.MapSceneFactory
|
||||
import com.pixelized.desktop.lwa.ui.screen.campaign.map.MapSceneViewModel
|
||||
import com.pixelized.desktop.lwa.ui.screen.levelup.LevelUpFactory
|
||||
import com.pixelized.desktop.lwa.ui.screen.levelup.LevelUpViewModel
|
||||
import com.pixelized.desktop.lwa.ui.screen.settings.SettingsViewModel
|
||||
|
|
@ -125,6 +130,7 @@ val storeDependencies
|
|||
singleOf(::TagStore)
|
||||
singleOf(::ItemStore)
|
||||
singleOf(::InventoryStore)
|
||||
singleOf(::MapStore)
|
||||
}
|
||||
|
||||
val repositoryDependencies
|
||||
|
|
@ -138,6 +144,8 @@ val repositoryDependencies
|
|||
singleOf(::TagRepository)
|
||||
singleOf(::ItemRepository)
|
||||
singleOf(::InventoryRepository)
|
||||
singleOf(::ImageResourcesRepository)
|
||||
singleOf(::MapRepository)
|
||||
}
|
||||
|
||||
val factoryDependencies
|
||||
|
|
@ -145,6 +153,7 @@ val factoryDependencies
|
|||
factoryOf(::NetworkFactory)
|
||||
factoryOf(::SettingsFactory)
|
||||
factoryOf(::CampaignJsonFactory)
|
||||
factoryOf(::MapSceneFactory)
|
||||
factoryOf(::CharacterRibbonFactory)
|
||||
factoryOf(::CharacterDetailHeaderFactory)
|
||||
factoryOf(::CharacterDetailSheetFactory)
|
||||
|
|
@ -175,6 +184,7 @@ val viewModelDependencies
|
|||
viewModelOf(::RollViewModel)
|
||||
viewModelOf(::NetworkViewModel)
|
||||
viewModelOf(::PlayerRibbonViewModel)
|
||||
viewModelOf(::MapSceneViewModel)
|
||||
viewModelOf(::NpcRibbonViewModel)
|
||||
viewModelOf(::CharacterDetailPanelViewModel)
|
||||
viewModelOf(::CharacterSheetDiminishedDialogViewModel)
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import com.pixelized.shared.lwa.model.campaign.CampaignJson
|
|||
import com.pixelized.shared.lwa.model.characterSheet.CharacterSheetJson
|
||||
import com.pixelized.shared.lwa.model.inventory.InventoryJson
|
||||
import com.pixelized.shared.lwa.model.item.ItemJson
|
||||
import com.pixelized.shared.lwa.model.map.MapJson
|
||||
import com.pixelized.shared.lwa.model.tag.TagJson
|
||||
import com.pixelized.shared.lwa.protocol.rest.APIResponse
|
||||
import com.pixelized.shared.lwa.protocol.rest.ApiPurseJson
|
||||
|
|
@ -165,6 +166,17 @@ interface LwaClient {
|
|||
|
||||
suspend fun getItemTags(): APIResponse<List<TagJson>>
|
||||
|
||||
suspend fun getMaps(): APIResponse<List<MapJson>>
|
||||
|
||||
suspend fun getMap(
|
||||
mapId: String,
|
||||
): APIResponse<MapJson>
|
||||
|
||||
suspend fun putMap(
|
||||
mapJson: MapJson,
|
||||
create: Boolean,
|
||||
): APIResponse<Unit>
|
||||
|
||||
companion object {
|
||||
fun error(error: APIResponse<*>): Nothing = throw LwaNetworkException(error)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import com.pixelized.shared.lwa.model.campaign.CampaignJson
|
|||
import com.pixelized.shared.lwa.model.characterSheet.CharacterSheetJson
|
||||
import com.pixelized.shared.lwa.model.inventory.InventoryJson
|
||||
import com.pixelized.shared.lwa.model.item.ItemJson
|
||||
import com.pixelized.shared.lwa.model.map.MapJson
|
||||
import com.pixelized.shared.lwa.model.tag.TagJson
|
||||
import com.pixelized.shared.lwa.protocol.rest.APIResponse
|
||||
import com.pixelized.shared.lwa.protocol.rest.ApiPurseJson
|
||||
|
|
@ -282,4 +283,25 @@ class LwaClientImpl(
|
|||
override suspend fun getItemTags(): APIResponse<List<TagJson>> = client
|
||||
.get("$root/tag/item")
|
||||
.body()
|
||||
|
||||
@Throws
|
||||
override suspend fun getMaps(): APIResponse<List<MapJson>> = client
|
||||
.get("$root/map/all")
|
||||
.body()
|
||||
|
||||
@Throws
|
||||
override suspend fun getMap(mapId: String): APIResponse<MapJson> = client
|
||||
.get("$root/map/detail?mapId=$mapId")
|
||||
.body()
|
||||
|
||||
@Throws
|
||||
override suspend fun putMap(
|
||||
mapJson: MapJson,
|
||||
create: Boolean,
|
||||
): APIResponse<Unit> = client
|
||||
.put("$root/map/update") {
|
||||
contentType(ContentType.Application.Json)
|
||||
setBody(mapJson)
|
||||
}
|
||||
.body<APIResponse<Unit>>()
|
||||
}
|
||||
|
|
@ -3,6 +3,7 @@ package com.pixelized.desktop.lwa.repository.campaign
|
|||
import com.pixelized.desktop.lwa.network.LwaClient
|
||||
import com.pixelized.desktop.lwa.repository.network.NetworkRepository
|
||||
import com.pixelized.shared.lwa.model.campaign.Campaign
|
||||
import com.pixelized.shared.lwa.model.campaign.Campaign.*
|
||||
import com.pixelized.shared.lwa.model.campaign.factory.CampaignJsonFactory
|
||||
import com.pixelized.shared.lwa.protocol.websocket.CampaignEvent
|
||||
import com.pixelized.shared.lwa.protocol.websocket.GameMasterEvent
|
||||
|
|
@ -141,11 +142,21 @@ class CampaignStore(
|
|||
)
|
||||
}
|
||||
|
||||
is CampaignEvent.UpdateScene -> campaignFlow.update { campaign ->
|
||||
is CampaignEvent.SceneUpdated -> campaignFlow.update { campaign ->
|
||||
campaign.copy(
|
||||
scene = Campaign.Scene(name = message.name)
|
||||
scene = Scene(name = message.name)
|
||||
)
|
||||
}
|
||||
|
||||
is CampaignEvent.MapUpdated -> campaignFlow.update { campaign ->
|
||||
campaign.copy(
|
||||
map = factory.convertFromJson(message.map),
|
||||
)
|
||||
}
|
||||
|
||||
is CampaignEvent.MapDeleted -> campaignFlow.update { campaign ->
|
||||
campaign.copy(map = null,)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,38 @@
|
|||
package com.pixelized.desktop.lwa.repository.map
|
||||
|
||||
import com.pixelized.desktop.lwa.repository.campaign.CampaignStore
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.combine
|
||||
import com.pixelized.shared.lwa.model.map.Map as MiniMap
|
||||
|
||||
class MapRepository(
|
||||
private val mapStore: MapStore,
|
||||
private val campaignStore: CampaignStore,
|
||||
) {
|
||||
fun mapsFlow(): StateFlow<Map<String, MiniMap>> = mapStore.mapsFlow()
|
||||
|
||||
fun maps() = mapStore.mapsFlow().value
|
||||
|
||||
fun currentMap(): Flow<MiniMap?> = combine(
|
||||
campaignStore.campaignFlow(),
|
||||
mapStore.mapsFlow(),
|
||||
) { campaign, maps ->
|
||||
maps[campaign.map?.id]
|
||||
}
|
||||
|
||||
suspend fun updateMapFlow() {
|
||||
mapStore.updateMapFlow()
|
||||
}
|
||||
|
||||
@Throws
|
||||
suspend fun updateMap(
|
||||
map: MiniMap,
|
||||
create: Boolean,
|
||||
) {
|
||||
mapStore.putMap(
|
||||
map = map,
|
||||
create = create,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,107 @@
|
|||
package com.pixelized.desktop.lwa.repository.map
|
||||
|
||||
import com.pixelized.desktop.lwa.network.LwaClient
|
||||
import com.pixelized.desktop.lwa.repository.network.NetworkRepository
|
||||
import com.pixelized.shared.lwa.model.map.factory.MapJsonFactory
|
||||
import com.pixelized.shared.lwa.protocol.websocket.ApiSynchronisation
|
||||
import com.pixelized.shared.lwa.protocol.websocket.SocketMessage
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.update
|
||||
import kotlinx.coroutines.launch
|
||||
import com.pixelized.shared.lwa.model.map.Map as MiniMap
|
||||
|
||||
class MapStore(
|
||||
private val network: NetworkRepository,
|
||||
private val factory: MapJsonFactory,
|
||||
private val client: LwaClient,
|
||||
) {
|
||||
private val mapsFlow = MutableStateFlow<Map<String, MiniMap>>(emptyMap())
|
||||
|
||||
init {
|
||||
val scope = CoroutineScope(Dispatchers.IO + Job())
|
||||
// data update through WebSocket.
|
||||
scope.launch {
|
||||
network.data.collect(::handleMessage)
|
||||
}
|
||||
}
|
||||
|
||||
fun mapsFlow(): StateFlow<Map<String, MiniMap>> = mapsFlow
|
||||
|
||||
suspend fun updateMapFlow() {
|
||||
val map = try {
|
||||
getMaps()
|
||||
} catch (exception: Exception) {
|
||||
println(exception.message) // TODO proper exception handling
|
||||
null
|
||||
}
|
||||
// guard case if getItem failed
|
||||
if (map == null) return
|
||||
// update the flow with the new item.
|
||||
mapsFlow.update { map }
|
||||
}
|
||||
|
||||
@Throws
|
||||
private suspend fun getMaps(): Map<String, MiniMap> {
|
||||
val request = client.getMaps()
|
||||
return when (request.success) {
|
||||
true -> request.data
|
||||
?.map { factory.convertFromJson(it) }
|
||||
?.associateBy { it.id }
|
||||
?: emptyMap()
|
||||
|
||||
else -> LwaClient.error(error = request)
|
||||
}
|
||||
}
|
||||
|
||||
@Throws
|
||||
private suspend fun getMap(id: String): MiniMap? {
|
||||
val request = client.getMap(mapId = id)
|
||||
return when (request.success) {
|
||||
true -> request.data?.let { factory.convertFromJson(it) }
|
||||
else -> LwaClient.error(error = request)
|
||||
}
|
||||
}
|
||||
|
||||
@Throws
|
||||
suspend fun putMap(
|
||||
map: MiniMap,
|
||||
create: Boolean,
|
||||
) {
|
||||
val request = client.putMap(
|
||||
mapJson = factory.convertToJson(item = map),
|
||||
create = create,
|
||||
)
|
||||
if (request.success.not()) {
|
||||
LwaClient.error(error = request)
|
||||
}
|
||||
}
|
||||
|
||||
// region: WebSocket & data update.
|
||||
|
||||
private suspend fun handleMessage(message: SocketMessage) {
|
||||
when (message) {
|
||||
is ApiSynchronisation.MapApiSynchronisation -> when (message) {
|
||||
is ApiSynchronisation.MapUpdate -> mapsFlow.update { maps ->
|
||||
maps.toMutableMap().also {
|
||||
when (val map = getMap(id = message.id)) {
|
||||
null -> it.remove(key = message.id)
|
||||
else -> it[message.id] = map
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
is ApiSynchronisation.MapDelete -> mapsFlow.update { maps ->
|
||||
maps.toMutableMap().also { it.remove(message.id) }
|
||||
}
|
||||
}
|
||||
|
||||
else -> Unit
|
||||
}
|
||||
}
|
||||
|
||||
// endregion
|
||||
}
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
package com.pixelized.desktop.lwa.repository.resources
|
||||
|
||||
import androidx.compose.ui.graphics.ImageBitmap
|
||||
import androidx.compose.ui.graphics.toComposeImageBitmap
|
||||
import io.ktor.client.HttpClient
|
||||
import io.ktor.client.request.get
|
||||
import io.ktor.client.statement.readRawBytes
|
||||
import org.jetbrains.skia.Image
|
||||
|
||||
class ImageResourcesRepository(
|
||||
private val httpClient: HttpClient,
|
||||
) {
|
||||
suspend fun load(url: String): ImageBitmap {
|
||||
try {
|
||||
val byteArray = httpClient.get(url).readRawBytes()
|
||||
val skiaImage = Image.makeFromEncoded(byteArray)
|
||||
return skiaImage.toComposeImageBitmap()
|
||||
} catch (_: Exception) {
|
||||
// TODO proper exception handling (error bus ?)
|
||||
return ImageBitmap(width = 0, height = 0)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -17,15 +17,10 @@ import com.pixelized.desktop.lwa.ui.composable.scene.drawables.SceneLayer
|
|||
|
||||
@Stable
|
||||
data class Scene(
|
||||
val size: IntSize,
|
||||
val layers: List<SceneLayer>,
|
||||
val elements: List<SceneElement>,
|
||||
) {
|
||||
val size: IntSize = IntSize(
|
||||
width = layers.maxOf { it.size.width },
|
||||
height = layers.maxOf { it.size.height },
|
||||
)
|
||||
}
|
||||
|
||||
)
|
||||
|
||||
@Composable
|
||||
fun Scene(
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@ class Camera(
|
|||
val offset: IntOffset by _offset
|
||||
|
||||
private var _sceneSize: IntSize by mutableStateOf(IntSize.Zero)
|
||||
|
||||
private var _cameraSize: IntSize by mutableStateOf(IntSize.Zero)
|
||||
val cameraSize: IntSize get() = _cameraSize
|
||||
val cameraSizeZoomed: IntSize by derivedStateOf {
|
||||
|
|
@ -40,7 +41,13 @@ class Camera(
|
|||
_sceneSize = sceneSize
|
||||
}
|
||||
|
||||
fun handlePanning(
|
||||
fun move(
|
||||
offset: IntOffset,
|
||||
) {
|
||||
_offset.value = offset
|
||||
}
|
||||
|
||||
fun pan(
|
||||
delta: Offset,
|
||||
) {
|
||||
val value = _offset.value - IntOffset(
|
||||
|
|
@ -50,7 +57,7 @@ class Camera(
|
|||
_offset.value = value
|
||||
}
|
||||
|
||||
fun handleZoom(
|
||||
fun zoom(
|
||||
power: Float,
|
||||
target: IntOffset? = null,
|
||||
) {
|
||||
|
|
|
|||
|
|
@ -33,7 +33,7 @@ fun Modifier.onCameraControl(
|
|||
event = event,
|
||||
) { delta ->
|
||||
when {
|
||||
event.buttons.isTertiaryPressed || (event.keyboardModifiers.isCtrlPressed && event.buttons.isPrimaryPressed) -> camera.handlePanning(
|
||||
event.buttons.isTertiaryPressed || (event.keyboardModifiers.isCtrlPressed && event.buttons.isPrimaryPressed) -> camera.pan(
|
||||
delta = delta,
|
||||
)
|
||||
}
|
||||
|
|
@ -41,7 +41,7 @@ fun Modifier.onCameraControl(
|
|||
}
|
||||
.onPointerEvent(PointerEventType.Scroll) { event: PointerEvent ->
|
||||
val change = event.changes.first()
|
||||
camera.handleZoom(
|
||||
camera.zoom(
|
||||
power = -change.scrollDelta.y.sign * 0.15f,
|
||||
target = change.position.round(),
|
||||
)
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ import androidx.compose.ui.text.buildAnnotatedString
|
|||
import androidx.compose.ui.text.withStyle
|
||||
import androidx.compose.ui.unit.Dp
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.round
|
||||
import com.pixelized.desktop.lwa.ui.composable.scene.camera.Camera
|
||||
import com.pixelized.desktop.lwa.ui.composable.scene.cursor.Cursor
|
||||
import com.pixelized.desktop.lwa.ui.composable.scene.utils.local
|
||||
|
|
@ -62,7 +63,7 @@ fun CursorDebugPanel(
|
|||
style = MaterialTheme.lwa.typography.debug.propertyValue,
|
||||
text = buildAnnotatedString {
|
||||
withStyle(style.propertyIdSpan) { append("local: ") }
|
||||
append(cursor.offset.local(camera).toString())
|
||||
append(cursor.offset.local(camera).round().toString())
|
||||
},
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ open class SceneDrawable(
|
|||
) {
|
||||
private val _alpha = Animatable(
|
||||
initialValue = initialAlpha,
|
||||
typeConverter = Float.Companion.VectorConverter,
|
||||
typeConverter = Float.VectorConverter,
|
||||
)
|
||||
val alpha get() = _alpha.value
|
||||
|
||||
|
|
|
|||
|
|
@ -44,7 +44,6 @@ import com.pixelized.desktop.lwa.ui.composable.character.diminished.CharacterShe
|
|||
import com.pixelized.desktop.lwa.ui.composable.character.diminished.CharacterSheetDiminishedDialogViewModel
|
||||
import com.pixelized.desktop.lwa.ui.composable.error.ErrorSnackHandler
|
||||
import com.pixelized.desktop.lwa.ui.composable.key.KeyHandler
|
||||
import com.pixelized.desktop.lwa.ui.screen.campaign.map.MapScene
|
||||
import com.pixelized.desktop.lwa.ui.navigation.screen.LocalScreenController
|
||||
import com.pixelized.desktop.lwa.ui.navigation.screen.destination.navigateToLevelScreen
|
||||
import com.pixelized.desktop.lwa.ui.overlay.portrait.PortraitOverlay
|
||||
|
|
@ -94,7 +93,7 @@ fun CampaignScreen(
|
|||
modifier = Modifier.fillMaxSize(),
|
||||
top = {
|
||||
CampaignToolbar(
|
||||
viewModel = campaignViewModel,
|
||||
toolBarViewModel = campaignViewModel,
|
||||
)
|
||||
},
|
||||
bottom = {
|
||||
|
|
|
|||
|
|
@ -142,9 +142,11 @@ class TextMessageFactory(
|
|||
is CampaignEvent -> when (message) {
|
||||
is CampaignEvent.CharacterAdded -> null
|
||||
is CampaignEvent.CharacterRemoved -> null
|
||||
is CampaignEvent.UpdateScene -> null
|
||||
is CampaignEvent.NpcAdded -> null
|
||||
is CampaignEvent.NpcRemoved -> null
|
||||
is CampaignEvent.SceneUpdated -> null
|
||||
is CampaignEvent.MapUpdated -> null
|
||||
is CampaignEvent.MapDeleted -> null
|
||||
}
|
||||
|
||||
is GameMasterEvent -> null
|
||||
|
|
|
|||
|
|
@ -1,6 +1,8 @@
|
|||
package com.pixelized.desktop.lwa.ui.screen.campaign.map
|
||||
|
||||
import androidx.compose.animation.AnimatedVisibility
|
||||
import androidx.compose.animation.fadeIn
|
||||
import androidx.compose.animation.fadeOut
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
|
|
@ -12,21 +14,19 @@ import androidx.compose.material.Icon
|
|||
import androidx.compose.material.IconButton
|
||||
import androidx.compose.material.MaterialTheme
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.State
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.unit.IntOffset
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import com.pixelized.desktop.lwa.ui.composable.scene.Scene
|
||||
import com.pixelized.desktop.lwa.ui.composable.scene.camera.Camera
|
||||
import com.pixelized.desktop.lwa.ui.composable.scene.cursor.Cursor
|
||||
import com.pixelized.desktop.lwa.ui.composable.scene.cursor.onCursorControl
|
||||
import com.pixelized.desktop.lwa.ui.composable.scene.debug.CameraDebugPanel
|
||||
import com.pixelized.desktop.lwa.ui.composable.scene.debug.CursorDebugPanel
|
||||
import com.pixelized.desktop.lwa.ui.composable.scene.debug.SceneDebugPanel
|
||||
import com.pixelized.desktop.lwa.ui.composable.scene.drawables.rememberLayoutFromResource
|
||||
import com.pixelized.desktop.lwa.ui.theme.lwa
|
||||
import kotlinx.coroutines.launch
|
||||
import lwacharactersheet.composeapp.generated.resources.Res
|
||||
|
|
@ -35,46 +35,25 @@ import lwacharactersheet.composeapp.generated.resources.ic_frame_bug_24dp
|
|||
import lwacharactersheet.composeapp.generated.resources.ic_visibility_24dp
|
||||
import lwacharactersheet.composeapp.generated.resources.ic_zoom_in_map_24dp
|
||||
import lwacharactersheet.composeapp.generated.resources.ic_zoom_out_map_24dp
|
||||
import lwacharactersheet.composeapp.generated.resources.image_dahome_maps
|
||||
import lwacharactersheet.composeapp.generated.resources.image_dahome_regions
|
||||
import org.jetbrains.compose.resources.painterResource
|
||||
import org.koin.compose.viewmodel.koinViewModel
|
||||
|
||||
@Composable
|
||||
fun MapScene(
|
||||
modifier: Modifier = Modifier,
|
||||
enableDebug: Boolean,
|
||||
viewModel: MapSceneViewModel = koinViewModel(),
|
||||
) {
|
||||
val scope = rememberCoroutineScope()
|
||||
|
||||
val map = rememberLayoutFromResource(
|
||||
name = "Dahomé",
|
||||
resource = Res.drawable.image_dahome_maps,
|
||||
)
|
||||
val mapRegionOverlay = rememberLayoutFromResource(
|
||||
name = "Région",
|
||||
resource = Res.drawable.image_dahome_regions,
|
||||
)
|
||||
val camera = remember {
|
||||
Camera(
|
||||
initialZoom = 1f,
|
||||
initialOffset = IntOffset(x = 1407, y = 1520),
|
||||
)
|
||||
}
|
||||
val cursor = remember {
|
||||
Cursor()
|
||||
}
|
||||
val scene = remember(map, mapRegionOverlay) {
|
||||
Scene(
|
||||
layers = listOf(map, mapRegionOverlay),
|
||||
elements = emptyList(),
|
||||
)
|
||||
}
|
||||
val openDebugMenu = remember {
|
||||
mutableStateOf(false)
|
||||
}
|
||||
val camera by viewModel.camera.collectAsStateWithLifecycle()
|
||||
val scene by viewModel.scene.collectAsStateWithLifecycle()
|
||||
val openDebugMenu = viewModel.displayDebugMenu.collectAsStateWithLifecycle()
|
||||
val enableDebug = viewModel.enableDebug.collectAsStateWithLifecycle()
|
||||
val enableLayersToggle = viewModel.enableLayersToggle.collectAsStateWithLifecycle()
|
||||
|
||||
Scene(
|
||||
modifier = Modifier
|
||||
.onCursorControl(camera = camera, cursor = cursor)
|
||||
.onCursorControl(camera = camera, cursor = viewModel.cursor)
|
||||
.then(other = modifier),
|
||||
scene = scene,
|
||||
camera = camera,
|
||||
|
|
@ -84,11 +63,12 @@ fun MapScene(
|
|||
.align(alignment = Alignment.BottomEnd)
|
||||
.padding(paddingValues = MaterialTheme.lwa.dimen.paddingValues),
|
||||
enableDebug = enableDebug,
|
||||
enableLayersToggle = enableLayersToggle,
|
||||
onZoomIn = {
|
||||
camera.handleZoom(power = 0.15f)
|
||||
camera.zoom(power = 0.15f)
|
||||
},
|
||||
onZoomOut = {
|
||||
camera.handleZoom(power = -0.15f)
|
||||
camera.zoom(power = -0.15f)
|
||||
},
|
||||
onResetCamera = {
|
||||
camera.resetPosition()
|
||||
|
|
@ -96,14 +76,12 @@ fun MapScene(
|
|||
},
|
||||
onToggleLayer = {
|
||||
scope.launch {
|
||||
scene.layers.getOrNull(1)?.let {
|
||||
scene.layers.drop(1).forEach {
|
||||
it.alpha(alpha = if (it.alpha == 0f) 1f else 0f)
|
||||
}
|
||||
}
|
||||
},
|
||||
onToggleDebug = {
|
||||
openDebugMenu.value = openDebugMenu.value.not()
|
||||
},
|
||||
onToggleDebug = viewModel::toggleDebugMenu,
|
||||
)
|
||||
|
||||
AnimatedVisibility(
|
||||
|
|
@ -128,7 +106,7 @@ fun MapScene(
|
|||
)
|
||||
CursorDebugPanel(
|
||||
camera = camera,
|
||||
cursors = remember { listOf(cursor) },
|
||||
cursors = remember { listOf(viewModel.cursor) },
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -139,7 +117,8 @@ fun MapScene(
|
|||
@Composable
|
||||
private fun MapActions(
|
||||
modifier: Modifier = Modifier,
|
||||
enableDebug: Boolean,
|
||||
enableDebug: State<Boolean>,
|
||||
enableLayersToggle: State<Boolean>,
|
||||
onZoomIn: () -> Unit,
|
||||
onZoomOut: () -> Unit,
|
||||
onResetCamera: () -> Unit,
|
||||
|
|
@ -173,16 +152,24 @@ private fun MapActions(
|
|||
contentDescription = null
|
||||
)
|
||||
}
|
||||
IconButton(
|
||||
onClick = onToggleLayer,
|
||||
AnimatedVisibility(
|
||||
visible = enableLayersToggle.value,
|
||||
enter = fadeIn(),
|
||||
exit = fadeOut(),
|
||||
) {
|
||||
Icon(
|
||||
painter = painterResource(Res.drawable.ic_visibility_24dp),
|
||||
contentDescription = null
|
||||
)
|
||||
IconButton(
|
||||
onClick = onToggleLayer,
|
||||
) {
|
||||
Icon(
|
||||
painter = painterResource(Res.drawable.ic_visibility_24dp),
|
||||
contentDescription = null
|
||||
)
|
||||
}
|
||||
}
|
||||
AnimatedVisibility(
|
||||
visible = enableDebug,
|
||||
visible = enableDebug.value,
|
||||
enter = fadeIn(),
|
||||
exit = fadeOut(),
|
||||
) {
|
||||
IconButton(
|
||||
onClick = onToggleDebug,
|
||||
|
|
@ -0,0 +1,44 @@
|
|||
package com.pixelized.desktop.lwa.ui.screen.campaign.map
|
||||
|
||||
import androidx.compose.ui.graphics.ImageBitmap
|
||||
import androidx.compose.ui.unit.IntOffset
|
||||
import androidx.compose.ui.unit.IntSize
|
||||
import com.pixelized.desktop.lwa.ui.composable.scene.Scene
|
||||
import com.pixelized.desktop.lwa.ui.composable.scene.camera.Camera
|
||||
import com.pixelized.desktop.lwa.ui.composable.scene.drawables.SceneLayer
|
||||
import com.pixelized.shared.lwa.model.map.Map
|
||||
|
||||
class MapSceneFactory {
|
||||
|
||||
suspend fun convertToScene(
|
||||
map: Map,
|
||||
image: suspend (String) -> ImageBitmap,
|
||||
): Scene {
|
||||
return Scene(
|
||||
size = IntSize(
|
||||
width = map.size.width,
|
||||
height = map.size.height,
|
||||
),
|
||||
layers = map.resources.map { resource ->
|
||||
SceneLayer(
|
||||
id = resource.id,
|
||||
name = resource.name,
|
||||
texture = image(resource.uri),
|
||||
)
|
||||
},
|
||||
elements = emptyList(),
|
||||
)
|
||||
}
|
||||
|
||||
fun convertToCamera(
|
||||
camera: Map.Camera
|
||||
) : Camera {
|
||||
return Camera(
|
||||
initialZoom = camera.zoom,
|
||||
initialOffset = IntOffset(
|
||||
x = camera.offsetX,
|
||||
y = camera.offsetY,
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,96 @@
|
|||
package com.pixelized.desktop.lwa.ui.screen.campaign.map
|
||||
|
||||
import androidx.compose.ui.unit.IntOffset
|
||||
import androidx.compose.ui.unit.IntSize
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.pixelized.desktop.lwa.repository.campaign.CampaignRepository
|
||||
import com.pixelized.desktop.lwa.repository.map.MapRepository
|
||||
import com.pixelized.desktop.lwa.repository.resources.ImageResourcesRepository
|
||||
import com.pixelized.desktop.lwa.repository.settings.SettingsRepository
|
||||
import com.pixelized.desktop.lwa.ui.composable.scene.Scene
|
||||
import com.pixelized.desktop.lwa.ui.composable.scene.camera.Camera
|
||||
import com.pixelized.desktop.lwa.ui.composable.scene.cursor.Cursor
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.SharingStarted
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.combine
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.flow.mapNotNull
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import kotlinx.coroutines.flow.stateIn
|
||||
|
||||
class MapSceneViewModel(
|
||||
resourcesRepository: ImageResourcesRepository,
|
||||
settingsRepository: SettingsRepository,
|
||||
campaignRepository: CampaignRepository,
|
||||
mapRepository: MapRepository,
|
||||
factory: MapSceneFactory,
|
||||
) : ViewModel() {
|
||||
|
||||
private val _displayDebugMenu = MutableStateFlow(false)
|
||||
val displayDebugMenu: StateFlow<Boolean> get() = _displayDebugMenu
|
||||
|
||||
val enableDebug = settingsRepository.settingsFlow()
|
||||
.map { it.isGameMaster ?: false }
|
||||
.stateIn(
|
||||
scope = viewModelScope,
|
||||
started = SharingStarted.Lazily,
|
||||
initialValue = false,
|
||||
)
|
||||
|
||||
private val _camera = MutableStateFlow(defaultCamera())
|
||||
val camera: StateFlow<Camera> = _camera
|
||||
|
||||
private val _scene = MutableStateFlow(defaultScene())
|
||||
val scene: StateFlow<Scene> = _scene
|
||||
|
||||
val enableLayersToggle: StateFlow<Boolean> = scene
|
||||
.map { it.layers.size > 1 }
|
||||
.stateIn(
|
||||
scope = viewModelScope,
|
||||
started = SharingStarted.Lazily,
|
||||
initialValue = false,
|
||||
)
|
||||
|
||||
val cursor = Cursor()
|
||||
|
||||
init {
|
||||
combine(
|
||||
campaignRepository.campaignFlow(),
|
||||
mapRepository.currentMap(),
|
||||
) { campaign, map ->
|
||||
when (map) {
|
||||
null -> null
|
||||
else -> (campaign.map?.camera ?: map.camera) to map
|
||||
}
|
||||
}
|
||||
.mapNotNull { it }
|
||||
.onEach { (camera, map) ->
|
||||
_scene.value = factory.convertToScene(
|
||||
map = map,
|
||||
image = { resourcesRepository.load(it) },
|
||||
)
|
||||
_camera.value = factory.convertToCamera(
|
||||
camera = camera
|
||||
)
|
||||
}
|
||||
.launchIn(scope = viewModelScope)
|
||||
}
|
||||
|
||||
fun toggleDebugMenu() {
|
||||
_displayDebugMenu.value = _displayDebugMenu.value.not()
|
||||
}
|
||||
|
||||
private fun defaultCamera() = Camera(
|
||||
initialZoom = 1f,
|
||||
initialOffset = IntOffset.Zero,
|
||||
)
|
||||
|
||||
private fun defaultScene() = Scene(
|
||||
size = IntSize(width = 3840, height = 2160),
|
||||
layers = emptyList(),
|
||||
elements = emptyList()
|
||||
)
|
||||
}
|
||||
|
|
@ -26,6 +26,7 @@ import com.pixelized.desktop.lwa.ui.navigation.screen.LocalScreenController
|
|||
import com.pixelized.desktop.lwa.ui.navigation.screen.destination.navigateToSettings
|
||||
import com.pixelized.desktop.lwa.ui.navigation.window.destination.openGameMasterWindow
|
||||
import com.pixelized.desktop.lwa.ui.screen.campaign.map.MapScene
|
||||
import com.pixelized.desktop.lwa.ui.screen.campaign.map.MapSceneViewModel
|
||||
import com.pixelized.desktop.lwa.ui.screen.campaign.toolbar.links.ResourcesMenu
|
||||
import com.pixelized.desktop.lwa.ui.screen.campaign.toolbar.network.NetworkMenu
|
||||
import com.pixelized.desktop.lwa.ui.theme.lwa
|
||||
|
|
@ -49,20 +50,23 @@ data class CampaignMenuStateUio(
|
|||
|
||||
@Composable
|
||||
fun CampaignToolbar(
|
||||
viewModel: CampaignToolbarViewModel = koinViewModel(),
|
||||
toolBarViewModel: CampaignToolbarViewModel = koinViewModel(),
|
||||
mapViewModel: MapSceneViewModel = koinViewModel(),
|
||||
) {
|
||||
val windows = LocalWindowController.current
|
||||
val screen = LocalScreenController.current
|
||||
|
||||
val title = viewModel.title.collectAsStateWithLifecycle()
|
||||
val status = viewModel.status.collectAsStateWithLifecycle()
|
||||
val isAdmin = viewModel.isAdmin.collectAsStateWithLifecycle()
|
||||
val menusState = viewModel.menusState.collectAsStateWithLifecycle()
|
||||
val title = toolBarViewModel.title.collectAsStateWithLifecycle()
|
||||
val status = toolBarViewModel.status.collectAsStateWithLifecycle()
|
||||
val isAdmin = toolBarViewModel.isAdmin.collectAsStateWithLifecycle()
|
||||
val isMapEnable = toolBarViewModel.isMapEnable.collectAsStateWithLifecycle()
|
||||
val menusState = toolBarViewModel.menusState.collectAsStateWithLifecycle()
|
||||
|
||||
CampaignToolbarContent(
|
||||
title = title,
|
||||
status = status,
|
||||
isAdmin = isAdmin,
|
||||
displayMapButton = isMapEnable,
|
||||
menusState = menusState,
|
||||
onAdmin = {
|
||||
windows.openGameMasterWindow()
|
||||
|
|
@ -70,12 +74,18 @@ fun CampaignToolbar(
|
|||
onSettings = {
|
||||
screen.navigateToSettings()
|
||||
},
|
||||
onNetwork = viewModel::onNetwork,
|
||||
onResources = viewModel::onResources,
|
||||
onMap = viewModel::onMap,
|
||||
onDismissNetworkMenu = viewModel::onDismissNetworkMenu,
|
||||
onDismissResourcesMenu = viewModel::onDismissResourcesMenu,
|
||||
onDismissMapMenu = viewModel::onDismissMapMenu,
|
||||
onNetwork = toolBarViewModel::onNetwork,
|
||||
onResources = toolBarViewModel::onResources,
|
||||
onMap = toolBarViewModel::onMap,
|
||||
onDismissNetworkMenu = toolBarViewModel::onDismissNetworkMenu,
|
||||
onDismissResourcesMenu = toolBarViewModel::onDismissResourcesMenu,
|
||||
onDismissMapMenu = toolBarViewModel::onDismissMapMenu,
|
||||
map = {
|
||||
MapScene(
|
||||
modifier = Modifier.size(640.dp),
|
||||
viewModel = mapViewModel,
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -86,6 +96,7 @@ private fun CampaignToolbarContent(
|
|||
title: State<String>,
|
||||
status: State<NetworkRepository.Status>,
|
||||
isAdmin: State<Boolean>,
|
||||
displayMapButton: State<Boolean>,
|
||||
menusState: State<CampaignMenuStateUio>,
|
||||
onAdmin: () -> Unit,
|
||||
onNetwork: () -> Unit,
|
||||
|
|
@ -95,6 +106,7 @@ private fun CampaignToolbarContent(
|
|||
onDismissNetworkMenu: () -> Unit,
|
||||
onDismissResourcesMenu: () -> Unit,
|
||||
onDismissMapMenu: () -> Unit,
|
||||
map: @Composable () -> Unit,
|
||||
) {
|
||||
TopAppBar(
|
||||
modifier = modifier,
|
||||
|
|
@ -118,25 +130,27 @@ private fun CampaignToolbarContent(
|
|||
)
|
||||
}
|
||||
}
|
||||
IconButton(
|
||||
onClick = onMap,
|
||||
AnimatedVisibility(
|
||||
visible = displayMapButton.value,
|
||||
enter = fadeIn(),
|
||||
exit = fadeOut(),
|
||||
) {
|
||||
Icon(
|
||||
painter = painterResource(Res.drawable.ic_map_24dp),
|
||||
tint = MaterialTheme.colors.primary,
|
||||
contentDescription = null,
|
||||
)
|
||||
DropdownMenu(
|
||||
expanded = menusState.value.isMapMenuOpen,
|
||||
onDismissRequest = onDismissMapMenu,
|
||||
content = {
|
||||
MapScene(
|
||||
modifier = Modifier.size(640.dp),
|
||||
enableDebug = isAdmin.value,
|
||||
)
|
||||
},
|
||||
)
|
||||
IconButton(
|
||||
onClick = onMap,
|
||||
) {
|
||||
Icon(
|
||||
painter = painterResource(Res.drawable.ic_map_24dp),
|
||||
tint = MaterialTheme.colors.primary,
|
||||
contentDescription = null,
|
||||
)
|
||||
DropdownMenu(
|
||||
expanded = menusState.value.isMapMenuOpen,
|
||||
onDismissRequest = onDismissMapMenu,
|
||||
content = { map() },
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
IconButton(
|
||||
onClick = onResources,
|
||||
) {
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ package com.pixelized.desktop.lwa.ui.screen.campaign.toolbar
|
|||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.pixelized.desktop.lwa.repository.campaign.CampaignRepository
|
||||
import com.pixelized.desktop.lwa.repository.map.MapRepository
|
||||
import com.pixelized.desktop.lwa.repository.network.NetworkRepository
|
||||
import com.pixelized.desktop.lwa.repository.settings.SettingsRepository
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
|
|
@ -14,6 +15,7 @@ import kotlinx.coroutines.flow.update
|
|||
|
||||
class CampaignToolbarViewModel(
|
||||
campaignRepository: CampaignRepository,
|
||||
mapRepository: MapRepository,
|
||||
networkRepository: NetworkRepository,
|
||||
settingsRepository: SettingsRepository,
|
||||
) : ViewModel() {
|
||||
|
|
@ -45,6 +47,14 @@ class CampaignToolbarViewModel(
|
|||
initialValue = false,
|
||||
)
|
||||
|
||||
val isMapEnable = mapRepository.currentMap()
|
||||
.map { it != null }
|
||||
.stateIn(
|
||||
scope = viewModelScope,
|
||||
started = SharingStarted.Lazily,
|
||||
initialValue = false,
|
||||
)
|
||||
|
||||
val isGameMaster = settingsRepository.settingsFlow()
|
||||
.map { it.isGameMaster ?: false }
|
||||
.stateIn(
|
||||
|
|
|
|||
|
|
@ -1,17 +1,19 @@
|
|||
import com.pixelized.server.lwa.logics.ItemUsageLogic
|
||||
import com.pixelized.server.lwa.model.alteration.AlterationService
|
||||
import com.pixelized.server.lwa.model.alteration.AlterationStore
|
||||
import com.pixelized.server.lwa.model.campaign.CampaignService
|
||||
import com.pixelized.server.lwa.model.campaign.CampaignStore
|
||||
import com.pixelized.server.lwa.model.character.CharacterSheetService
|
||||
import com.pixelized.server.lwa.model.character.CharacterSheetStore
|
||||
import com.pixelized.server.lwa.model.inventory.InventoryService
|
||||
import com.pixelized.server.lwa.model.inventory.InventoryStore
|
||||
import com.pixelized.server.lwa.model.item.ItemService
|
||||
import com.pixelized.server.lwa.model.item.ItemStore
|
||||
import com.pixelized.server.lwa.model.tag.TagService
|
||||
import com.pixelized.server.lwa.model.tag.TagStore
|
||||
import com.pixelized.server.lwa.services.alteration.AlterationService
|
||||
import com.pixelized.server.lwa.services.alteration.AlterationStore
|
||||
import com.pixelized.server.lwa.services.campaign.CampaignService
|
||||
import com.pixelized.server.lwa.services.campaign.CampaignStore
|
||||
import com.pixelized.server.lwa.services.character.CharacterSheetService
|
||||
import com.pixelized.server.lwa.services.character.CharacterSheetStore
|
||||
import com.pixelized.server.lwa.services.inventory.InventoryService
|
||||
import com.pixelized.server.lwa.services.inventory.InventoryStore
|
||||
import com.pixelized.server.lwa.services.item.ItemService
|
||||
import com.pixelized.server.lwa.services.item.ItemStore
|
||||
import com.pixelized.server.lwa.services.tag.TagService
|
||||
import com.pixelized.server.lwa.services.tag.TagStore
|
||||
import com.pixelized.server.lwa.server.Engine
|
||||
import com.pixelized.server.lwa.services.map.MapService
|
||||
import com.pixelized.server.lwa.services.map.MapStore
|
||||
import com.pixelized.shared.lwa.utils.PathProvider
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Job
|
||||
|
|
@ -51,6 +53,7 @@ val storeDependencies
|
|||
singleOf(::InventoryStore)
|
||||
singleOf(::ItemStore)
|
||||
singleOf(::TagStore)
|
||||
singleOf(::MapStore)
|
||||
}
|
||||
|
||||
val serviceDependencies
|
||||
|
|
@ -61,6 +64,7 @@ val serviceDependencies
|
|||
singleOf(::InventoryService)
|
||||
singleOf(::ItemService)
|
||||
singleOf(::TagService)
|
||||
singleOf(::MapService)
|
||||
}
|
||||
|
||||
val logicsDependencies
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
package com.pixelized.server.lwa.logics
|
||||
|
||||
import com.pixelized.server.lwa.model.character.CharacterSheetService
|
||||
import com.pixelized.server.lwa.model.inventory.InventoryService
|
||||
import com.pixelized.server.lwa.services.character.CharacterSheetService
|
||||
import com.pixelized.server.lwa.services.inventory.InventoryService
|
||||
|
||||
class ItemUsageLogic(
|
||||
private val characterSheetService: CharacterSheetService,
|
||||
|
|
|
|||
|
|
@ -1,18 +1,20 @@
|
|||
package com.pixelized.server.lwa.server
|
||||
|
||||
import com.pixelized.server.lwa.logics.ItemUsageLogic
|
||||
import com.pixelized.server.lwa.model.alteration.AlterationService
|
||||
import com.pixelized.server.lwa.model.alteration.AlterationStore
|
||||
import com.pixelized.server.lwa.model.campaign.CampaignService
|
||||
import com.pixelized.server.lwa.model.campaign.CampaignStore
|
||||
import com.pixelized.server.lwa.model.character.CharacterSheetService
|
||||
import com.pixelized.server.lwa.model.character.CharacterSheetStore
|
||||
import com.pixelized.server.lwa.model.inventory.InventoryService
|
||||
import com.pixelized.server.lwa.model.inventory.InventoryStore
|
||||
import com.pixelized.server.lwa.model.item.ItemService
|
||||
import com.pixelized.server.lwa.model.item.ItemStore
|
||||
import com.pixelized.server.lwa.model.tag.TagService
|
||||
import com.pixelized.server.lwa.model.tag.TagStore
|
||||
import com.pixelized.server.lwa.services.alteration.AlterationService
|
||||
import com.pixelized.server.lwa.services.alteration.AlterationStore
|
||||
import com.pixelized.server.lwa.services.campaign.CampaignService
|
||||
import com.pixelized.server.lwa.services.campaign.CampaignStore
|
||||
import com.pixelized.server.lwa.services.character.CharacterSheetService
|
||||
import com.pixelized.server.lwa.services.character.CharacterSheetStore
|
||||
import com.pixelized.server.lwa.services.inventory.InventoryService
|
||||
import com.pixelized.server.lwa.services.inventory.InventoryStore
|
||||
import com.pixelized.server.lwa.services.item.ItemService
|
||||
import com.pixelized.server.lwa.services.item.ItemStore
|
||||
import com.pixelized.server.lwa.services.map.MapService
|
||||
import com.pixelized.server.lwa.services.map.MapStore
|
||||
import com.pixelized.server.lwa.services.tag.TagService
|
||||
import com.pixelized.server.lwa.services.tag.TagStore
|
||||
import com.pixelized.shared.lwa.model.campaign.factory.CampaignJsonFactory
|
||||
import com.pixelized.shared.lwa.protocol.websocket.ApiSynchronisation
|
||||
import com.pixelized.shared.lwa.protocol.websocket.CampaignEvent
|
||||
|
|
@ -30,6 +32,7 @@ class Engine(
|
|||
val itemService: ItemService,
|
||||
val inventoryService: InventoryService,
|
||||
val tagService: TagService,
|
||||
val mapService: MapService,
|
||||
val campaignJsonFactory: CampaignJsonFactory,
|
||||
val itemUsageLogic: ItemUsageLogic,
|
||||
private val campaignStore: CampaignStore,
|
||||
|
|
@ -38,6 +41,7 @@ class Engine(
|
|||
private val itemStore: ItemStore,
|
||||
private val inventoryStore: InventoryStore,
|
||||
private val tagStore: TagStore,
|
||||
private val mapStore: MapStore,
|
||||
) {
|
||||
val webSocket = MutableSharedFlow<SocketMessage>()
|
||||
|
||||
|
|
@ -106,7 +110,19 @@ class Engine(
|
|||
characterSheetId = message.characterSheetId,
|
||||
)
|
||||
|
||||
is CampaignEvent.UpdateScene -> Unit
|
||||
is CampaignEvent.MapUpdated -> {
|
||||
// convert the map into the a usable data model.
|
||||
val map = campaignJsonFactory.convertFromJson(json = message.map)
|
||||
// update the map
|
||||
campaignService.setMap(map = map)
|
||||
}
|
||||
|
||||
is CampaignEvent.MapDeleted -> {
|
||||
// update the map
|
||||
campaignService.setMap(map = null)
|
||||
}
|
||||
|
||||
is CampaignEvent.SceneUpdated -> Unit
|
||||
}
|
||||
|
||||
is GameAdminEvent -> when (message) {
|
||||
|
|
@ -117,6 +133,7 @@ class Engine(
|
|||
itemStore.updateItemsFlow()
|
||||
inventoryStore.updateInventoryFlow()
|
||||
tagStore.updateTagFlow()
|
||||
mapStore.updateMapFlow()
|
||||
|
||||
webSocket.emit(
|
||||
value = GameAdminEvent.ServerSynchronization(
|
||||
|
|
|
|||
|
|
@ -5,7 +5,9 @@ import com.pixelized.server.lwa.server.rest.alteration.deleteAlteration
|
|||
import com.pixelized.server.lwa.server.rest.alteration.getAlteration
|
||||
import com.pixelized.server.lwa.server.rest.alteration.getAlterations
|
||||
import com.pixelized.server.lwa.server.rest.alteration.putAlteration
|
||||
import com.pixelized.server.lwa.server.rest.campaign.deleteCampaignMap
|
||||
import com.pixelized.server.lwa.server.rest.campaign.getCampaign
|
||||
import com.pixelized.server.lwa.server.rest.campaign.putCampaignMap
|
||||
import com.pixelized.server.lwa.server.rest.campaign.putCampaignCharacter
|
||||
import com.pixelized.server.lwa.server.rest.campaign.putCampaignNpc
|
||||
import com.pixelized.server.lwa.server.rest.campaign.putCampaignScene
|
||||
|
|
@ -23,9 +25,9 @@ import com.pixelized.server.lwa.server.rest.character.putCharacterFatigue
|
|||
import com.pixelized.server.lwa.server.rest.inventory.changeInventoryItemCount
|
||||
import com.pixelized.server.lwa.server.rest.inventory.consumeInventoryItem
|
||||
import com.pixelized.server.lwa.server.rest.inventory.createInventoryItem
|
||||
import com.pixelized.server.lwa.server.rest.inventory.equipInventoryItem
|
||||
import com.pixelized.server.lwa.server.rest.inventory.deleteInventory
|
||||
import com.pixelized.server.lwa.server.rest.inventory.deleteInventoryItem
|
||||
import com.pixelized.server.lwa.server.rest.inventory.equipInventoryItem
|
||||
import com.pixelized.server.lwa.server.rest.inventory.getInventory
|
||||
import com.pixelized.server.lwa.server.rest.inventory.putInventory
|
||||
import com.pixelized.server.lwa.server.rest.inventory.putPurse
|
||||
|
|
@ -33,6 +35,10 @@ import com.pixelized.server.lwa.server.rest.item.deleteItem
|
|||
import com.pixelized.server.lwa.server.rest.item.getItem
|
||||
import com.pixelized.server.lwa.server.rest.item.getItems
|
||||
import com.pixelized.server.lwa.server.rest.item.putItem
|
||||
import com.pixelized.server.lwa.server.rest.map.deleteMap
|
||||
import com.pixelized.server.lwa.server.rest.map.getMap
|
||||
import com.pixelized.server.lwa.server.rest.map.getMaps
|
||||
import com.pixelized.server.lwa.server.rest.map.putMap
|
||||
import com.pixelized.server.lwa.server.rest.tag.getAlterationTags
|
||||
import com.pixelized.server.lwa.server.rest.tag.getCharacterTags
|
||||
import com.pixelized.server.lwa.server.rest.tag.getItemTags
|
||||
|
|
@ -163,6 +169,18 @@ class LocalServer {
|
|||
path = "/scene",
|
||||
body = engine.putCampaignScene(),
|
||||
)
|
||||
route(
|
||||
path = "/map",
|
||||
) {
|
||||
put(
|
||||
path = "/update",
|
||||
body = engine.putCampaignMap(),
|
||||
)
|
||||
put(
|
||||
path = "/delete",
|
||||
body = engine.deleteCampaignMap(),
|
||||
)
|
||||
}
|
||||
}
|
||||
route(
|
||||
path = "/character",
|
||||
|
|
@ -306,6 +324,24 @@ class LocalServer {
|
|||
)
|
||||
}
|
||||
}
|
||||
route(path = "/map") {
|
||||
get(
|
||||
path = "/all",
|
||||
body = engine.getMaps(),
|
||||
)
|
||||
get(
|
||||
path = "/detail",
|
||||
body = engine.getMap(),
|
||||
)
|
||||
put(
|
||||
path = "/update",
|
||||
body = engine.putMap(),
|
||||
)
|
||||
delete(
|
||||
path = "/delete",
|
||||
body = engine.deleteMap(),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,25 @@
|
|||
{
|
||||
"id": "DAHOME_432_PU",
|
||||
"name": "Dahomé",
|
||||
"camera": {
|
||||
"zoom": 1.52,
|
||||
"offsetX": 1594,
|
||||
"offsetY": 1149
|
||||
},
|
||||
"size": {
|
||||
"width": 3840,
|
||||
"height": 2160
|
||||
},
|
||||
"resources": [
|
||||
{
|
||||
"id": "ROOT",
|
||||
"name": "Root",
|
||||
"uri": "https://i.ibb.co/7dvDJ50L/image-dahome-maps.webp"
|
||||
},
|
||||
{
|
||||
"id": "REGION",
|
||||
"name": "Frontières",
|
||||
"uri": "https://i.ibb.co/mC4jw8Yj/image-dahome-regions.webp"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -0,0 +1,32 @@
|
|||
package com.pixelized.server.lwa.server.rest.campaign
|
||||
|
||||
import com.pixelized.server.lwa.server.Engine
|
||||
import com.pixelized.server.lwa.utils.extentions.exception
|
||||
import com.pixelized.shared.lwa.protocol.rest.APIResponse
|
||||
import com.pixelized.shared.lwa.protocol.websocket.CampaignEvent
|
||||
import io.ktor.server.response.respond
|
||||
import io.ktor.server.routing.RoutingContext
|
||||
|
||||
fun Engine.deleteCampaignMap(): suspend RoutingContext.() -> Unit {
|
||||
return {
|
||||
try {
|
||||
// update the campaign.
|
||||
campaignService.setMap(
|
||||
map = null,
|
||||
)
|
||||
// API & WebSocket responses
|
||||
call.respond(
|
||||
message = APIResponse.success(),
|
||||
)
|
||||
webSocket.emit(
|
||||
value = CampaignEvent.MapDeleted(
|
||||
timestamp = System.currentTimeMillis(),
|
||||
)
|
||||
)
|
||||
} catch (exception: Exception) {
|
||||
call.exception(
|
||||
exception = exception
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,39 @@
|
|||
package com.pixelized.server.lwa.server.rest.campaign
|
||||
|
||||
import com.pixelized.server.lwa.server.Engine
|
||||
import com.pixelized.server.lwa.utils.extentions.exception
|
||||
import com.pixelized.shared.lwa.model.campaign.CampaignJsonV2
|
||||
import com.pixelized.shared.lwa.protocol.rest.APIResponse
|
||||
import com.pixelized.shared.lwa.protocol.websocket.CampaignEvent
|
||||
import io.ktor.server.request.receive
|
||||
import io.ktor.server.response.respond
|
||||
import io.ktor.server.routing.RoutingContext
|
||||
|
||||
fun Engine.putCampaignMap(): suspend RoutingContext.() -> Unit {
|
||||
return {
|
||||
try {
|
||||
// Get the map json from the body of the request
|
||||
val form = call.receive<CampaignJsonV2.MiniMapJson>()
|
||||
// convert the map into the a usable data model.
|
||||
val map = campaignJsonFactory.convertFromJson(json = form)
|
||||
// update the campaign.
|
||||
campaignService.setMap(
|
||||
map = map,
|
||||
)
|
||||
// API & WebSocket responses
|
||||
call.respond(
|
||||
message = APIResponse.success(),
|
||||
)
|
||||
webSocket.emit(
|
||||
value = CampaignEvent.MapUpdated(
|
||||
timestamp = System.currentTimeMillis(),
|
||||
map = form,
|
||||
)
|
||||
)
|
||||
} catch (exception: Exception) {
|
||||
call.exception(
|
||||
exception = exception
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -25,7 +25,7 @@ fun Engine.putCampaignScene(): suspend RoutingContext.() -> Unit {
|
|||
message = APIResponse.success(),
|
||||
)
|
||||
webSocket.emit(
|
||||
value = CampaignEvent.UpdateScene(
|
||||
value = CampaignEvent.SceneUpdated(
|
||||
timestamp = System.currentTimeMillis(),
|
||||
name = scene.name,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,36 @@
|
|||
package com.pixelized.server.lwa.server.rest.map
|
||||
|
||||
import com.pixelized.server.lwa.server.Engine
|
||||
import com.pixelized.server.lwa.utils.extentions.exception
|
||||
import com.pixelized.server.lwa.utils.extentions.mapId
|
||||
import com.pixelized.shared.lwa.protocol.rest.APIResponse
|
||||
import com.pixelized.shared.lwa.protocol.websocket.ApiSynchronisation
|
||||
import io.ktor.server.response.respond
|
||||
import io.ktor.server.routing.RoutingContext
|
||||
|
||||
fun Engine.deleteMap(): suspend RoutingContext.() -> Unit {
|
||||
return {
|
||||
try {
|
||||
// get the query parameter
|
||||
val mapId = call.queryParameters.mapId
|
||||
// delete the map.
|
||||
mapService.delete(
|
||||
mapId = mapId,
|
||||
)
|
||||
// API & WebSocket responses.
|
||||
call.respond(
|
||||
message = APIResponse.success(),
|
||||
)
|
||||
webSocket.emit(
|
||||
value = ApiSynchronisation.MapDelete(
|
||||
timestamp = System.currentTimeMillis(),
|
||||
id = mapId,
|
||||
),
|
||||
)
|
||||
} catch (exception: Exception) {
|
||||
call.exception(
|
||||
exception = exception,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,29 @@
|
|||
package com.pixelized.server.lwa.server.rest.map
|
||||
|
||||
import com.pixelized.server.lwa.server.Engine
|
||||
import com.pixelized.server.lwa.utils.extentions.exception
|
||||
import com.pixelized.server.lwa.utils.extentions.mapId
|
||||
import com.pixelized.shared.lwa.protocol.rest.APIResponse
|
||||
import io.ktor.server.response.respond
|
||||
import io.ktor.server.routing.RoutingContext
|
||||
|
||||
fun Engine.getMap(): suspend RoutingContext.() -> Unit {
|
||||
return {
|
||||
try {
|
||||
// get the query parameter
|
||||
val mapId = call.queryParameters.mapId
|
||||
// fetch the map
|
||||
val map = mapService.map(id = mapId)
|
||||
// respond to the request
|
||||
call.respond(
|
||||
message = APIResponse.success(
|
||||
data = map,
|
||||
),
|
||||
)
|
||||
} catch (exception: Exception) {
|
||||
call.exception(
|
||||
exception = exception
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
package com.pixelized.server.lwa.server.rest.map
|
||||
|
||||
import com.pixelized.server.lwa.server.Engine
|
||||
import com.pixelized.server.lwa.utils.extentions.exception
|
||||
import com.pixelized.shared.lwa.protocol.rest.APIResponse
|
||||
import com.pixelized.shared.lwa.protocol.websocket.ApiSynchronisation
|
||||
import io.ktor.server.response.respond
|
||||
import io.ktor.server.routing.RoutingContext
|
||||
|
||||
fun Engine.getMaps(): suspend RoutingContext.() -> Unit {
|
||||
return {
|
||||
try {
|
||||
call.respond(
|
||||
message = APIResponse.success(
|
||||
data = mapService.maps(),
|
||||
),
|
||||
)
|
||||
} catch (exception: Exception) {
|
||||
call.exception(
|
||||
exception = exception
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,37 @@
|
|||
package com.pixelized.server.lwa.server.rest.map
|
||||
|
||||
import com.pixelized.server.lwa.server.Engine
|
||||
import com.pixelized.server.lwa.utils.extentions.create
|
||||
import com.pixelized.server.lwa.utils.extentions.exception
|
||||
import com.pixelized.shared.lwa.model.alteration.AlterationJson
|
||||
import com.pixelized.shared.lwa.model.map.MapJson
|
||||
import com.pixelized.shared.lwa.protocol.rest.APIResponse
|
||||
import com.pixelized.shared.lwa.protocol.websocket.ApiSynchronisation
|
||||
import io.ktor.server.request.receive
|
||||
import io.ktor.server.response.respond
|
||||
import io.ktor.server.routing.RoutingContext
|
||||
|
||||
fun Engine.putMap(): suspend RoutingContext.() -> Unit {
|
||||
return {
|
||||
try {
|
||||
val form = call.receive<MapJson>()
|
||||
|
||||
mapService.save(
|
||||
json = form,
|
||||
)
|
||||
call.respond(
|
||||
message = APIResponse.success(),
|
||||
)
|
||||
webSocket.emit(
|
||||
value = ApiSynchronisation.MapUpdate(
|
||||
timestamp = System.currentTimeMillis(),
|
||||
id = form.id,
|
||||
),
|
||||
)
|
||||
} catch (exception: Exception) {
|
||||
call.exception(
|
||||
exception = exception,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package com.pixelized.server.lwa.model.alteration
|
||||
package com.pixelized.server.lwa.services.alteration
|
||||
|
||||
import com.pixelized.shared.lwa.model.alteration.AlterationJson
|
||||
import com.pixelized.shared.lwa.model.alteration.AlterationJsonFactory
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package com.pixelized.server.lwa.model.alteration
|
||||
package com.pixelized.server.lwa.services.alteration
|
||||
|
||||
import com.pixelized.server.lwa.server.exception.BusinessException
|
||||
import com.pixelized.server.lwa.server.exception.FileReadException
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package com.pixelized.server.lwa.model.campaign
|
||||
package com.pixelized.server.lwa.services.campaign
|
||||
|
||||
import com.pixelized.server.lwa.server.exception.BusinessException
|
||||
import com.pixelized.shared.lwa.model.campaign.Campaign
|
||||
|
|
@ -120,6 +120,16 @@ class CampaignService(
|
|||
)
|
||||
}
|
||||
|
||||
@Throws
|
||||
suspend fun setMap(
|
||||
map: Campaign.MiniMap?,
|
||||
) {
|
||||
// save the campaign to the disk + update the flow.
|
||||
store.save(
|
||||
campaign = campaign.copy(map = map)
|
||||
)
|
||||
}
|
||||
|
||||
// Data manipulation through WebSocket.
|
||||
|
||||
suspend fun updateToggleParty() {
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package com.pixelized.server.lwa.model.campaign
|
||||
package com.pixelized.server.lwa.services.campaign
|
||||
|
||||
import com.pixelized.server.lwa.server.exception.FileReadException
|
||||
import com.pixelized.server.lwa.server.exception.FileWriteException
|
||||
|
|
@ -10,7 +10,6 @@ import com.pixelized.shared.lwa.model.campaign.factory.CampaignJsonFactory
|
|||
import com.pixelized.shared.lwa.utils.PathProvider
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.update
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package com.pixelized.server.lwa.model.character
|
||||
package com.pixelized.server.lwa.services.character
|
||||
|
||||
import com.pixelized.shared.lwa.model.characterSheet.CharacterSheet
|
||||
import com.pixelized.shared.lwa.model.characterSheet.CharacterSheetJson
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package com.pixelized.server.lwa.model.character
|
||||
package com.pixelized.server.lwa.services.character
|
||||
|
||||
import com.pixelized.server.lwa.server.exception.BusinessException
|
||||
import com.pixelized.server.lwa.server.exception.FileReadException
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
package com.pixelized.server.lwa.model.inventory
|
||||
package com.pixelized.server.lwa.services.inventory
|
||||
|
||||
import com.pixelized.server.lwa.model.item.ItemStore
|
||||
import com.pixelized.server.lwa.services.item.ItemStore
|
||||
import com.pixelized.server.lwa.server.exception.BusinessException
|
||||
import com.pixelized.shared.lwa.model.inventory.Inventory
|
||||
import com.pixelized.shared.lwa.model.inventory.InventoryJson
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package com.pixelized.server.lwa.model.inventory
|
||||
package com.pixelized.server.lwa.services.inventory
|
||||
|
||||
import com.pixelized.server.lwa.server.exception.BusinessException
|
||||
import com.pixelized.server.lwa.server.exception.FileReadException
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package com.pixelized.server.lwa.model.item
|
||||
package com.pixelized.server.lwa.services.item
|
||||
|
||||
import com.pixelized.shared.lwa.model.item.ItemJson
|
||||
import com.pixelized.shared.lwa.model.item.factory.ItemJsonFactory
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package com.pixelized.server.lwa.model.item
|
||||
package com.pixelized.server.lwa.services.item
|
||||
|
||||
import com.pixelized.server.lwa.server.exception.BusinessException
|
||||
import com.pixelized.server.lwa.server.exception.FileReadException
|
||||
|
|
@ -0,0 +1,32 @@
|
|||
package com.pixelized.server.lwa.services.map
|
||||
|
||||
import com.pixelized.server.lwa.server.exception.BusinessException
|
||||
import com.pixelized.shared.lwa.model.map.MapJson
|
||||
|
||||
class MapService(
|
||||
private val mapStore: MapStore,
|
||||
) {
|
||||
fun maps(): List<MapJson> {
|
||||
return mapStore.maps().value.values.toList()
|
||||
}
|
||||
|
||||
@Throws
|
||||
fun map(id: String): MapJson {
|
||||
return mapStore.maps().value[id]
|
||||
?: throw BusinessException("Map with id:$id is not found")
|
||||
}
|
||||
|
||||
@Throws
|
||||
suspend fun save(
|
||||
json: MapJson,
|
||||
) {
|
||||
mapStore.save(
|
||||
map = json,
|
||||
)
|
||||
}
|
||||
|
||||
@Throws
|
||||
fun delete(mapId: String) {
|
||||
mapStore.delete(id = mapId)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,126 @@
|
|||
package com.pixelized.server.lwa.services.map
|
||||
|
||||
import com.pixelized.server.lwa.server.exception.BusinessException
|
||||
import com.pixelized.server.lwa.server.exception.FileReadException
|
||||
import com.pixelized.server.lwa.server.exception.FileWriteException
|
||||
import com.pixelized.server.lwa.server.exception.JsonCodingException
|
||||
import com.pixelized.server.lwa.server.exception.JsonConversionException
|
||||
import com.pixelized.shared.lwa.model.map.MapJson
|
||||
import com.pixelized.shared.lwa.utils.PathProvider
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.update
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import kotlinx.serialization.json.Json
|
||||
import java.io.File
|
||||
import java.text.Collator
|
||||
|
||||
class MapStore(
|
||||
private val pathProvider: PathProvider,
|
||||
private val jsonSerializer: Json,
|
||||
scope: CoroutineScope,
|
||||
) {
|
||||
private val directory = File(pathProvider.mapPath()).also { it.mkdirs() }
|
||||
private val mapsFlow = MutableStateFlow<Map<String, MapJson>>(emptyMap())
|
||||
|
||||
init {
|
||||
// make the file path.
|
||||
File(pathProvider.mapPath()).mkdirs()
|
||||
// load the initial data
|
||||
scope.launch {
|
||||
updateMapFlow()
|
||||
}
|
||||
}
|
||||
|
||||
fun maps(): StateFlow<Map<String, MapJson>> = mapsFlow
|
||||
|
||||
suspend fun updateMapFlow() {
|
||||
mapsFlow.value = try {
|
||||
load().associateBy { it.id }
|
||||
} catch (exception: Exception) {
|
||||
println(exception.message) // TODO proper exception handling
|
||||
emptyMap()
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun load(
|
||||
directory: File = this.directory,
|
||||
): List<MapJson> {
|
||||
return withContext(Dispatchers.IO) {
|
||||
directory
|
||||
.listFiles()
|
||||
?.mapNotNull { file ->
|
||||
val json = try {
|
||||
file.readText(charset = Charsets.UTF_8)
|
||||
} catch (exception: Exception) {
|
||||
throw FileReadException(root = exception)
|
||||
}
|
||||
// Guard, if the json is blank no character have been save, ignore this file.
|
||||
if (json.isBlank()) {
|
||||
return@mapNotNull null
|
||||
}
|
||||
// decode the file
|
||||
try {
|
||||
jsonSerializer.decodeFromString<MapJson>(json)
|
||||
} catch (exception: Exception) {
|
||||
throw JsonCodingException(root = exception)
|
||||
}
|
||||
}
|
||||
?: emptyList()
|
||||
}
|
||||
}
|
||||
|
||||
@Throws(JsonConversionException::class, FileWriteException::class)
|
||||
suspend fun save(map: MapJson) {
|
||||
// convert the data to json format
|
||||
val json = try {
|
||||
this.jsonSerializer.encodeToString(map)
|
||||
} catch (exception: Exception) {
|
||||
throw JsonConversionException(root = exception)
|
||||
}
|
||||
// write the file
|
||||
try {
|
||||
val file = mapFile(id = map.id)
|
||||
file.writeText(
|
||||
text = json,
|
||||
charset = Charsets.UTF_8,
|
||||
)
|
||||
} catch (exception: Exception) {
|
||||
throw FileWriteException(root = exception)
|
||||
}
|
||||
// Update the dataflow.
|
||||
mapsFlow.update { maps ->
|
||||
maps.toMutableMap().also {
|
||||
it[map.id] = map
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Throws(BusinessException::class)
|
||||
fun delete(id: String) {
|
||||
val file = mapFile(id = id)
|
||||
// Guard case on the file existence.
|
||||
if (file.exists().not()) {
|
||||
throw BusinessException(
|
||||
message = "Alteration doesn't not exist, deletion is impossible.",
|
||||
)
|
||||
}
|
||||
// Guard case on the file deletion
|
||||
if (file.delete().not()) {
|
||||
throw BusinessException(
|
||||
message = "Alteration file have not been deleted for unknown reason.",
|
||||
)
|
||||
}
|
||||
// Update the data model with the deleted alteration.
|
||||
mapsFlow.update { maps ->
|
||||
maps.toMutableMap().also {
|
||||
it.remove(key = id)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun mapFile(id: String) = File("${pathProvider.mapPath()}$id.json")
|
||||
}
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package com.pixelized.server.lwa.model.tag
|
||||
package com.pixelized.server.lwa.services.tag
|
||||
|
||||
import com.pixelized.shared.lwa.model.tag.TagJson
|
||||
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package com.pixelized.server.lwa.model.tag
|
||||
package com.pixelized.server.lwa.services.tag
|
||||
|
||||
import com.pixelized.server.lwa.server.exception.FileReadException
|
||||
import com.pixelized.server.lwa.server.exception.FileWriteException
|
||||
|
|
@ -6,8 +6,6 @@ import com.pixelized.server.lwa.server.exception.JsonConversionException
|
|||
import com.pixelized.shared.lwa.model.tag.TagJson
|
||||
import com.pixelized.shared.lwa.utils.PathProvider
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.launch
|
||||
|
|
@ -43,6 +43,12 @@ val Parameters.itemId: String
|
|||
code = APIResponse.ErrorCode.ItemId,
|
||||
)
|
||||
|
||||
val Parameters.mapId: String
|
||||
get() = param(
|
||||
name = "mapId",
|
||||
code = APIResponse.ErrorCode.ItemId,
|
||||
)
|
||||
|
||||
val Parameters.count: Float
|
||||
get() = param(
|
||||
name = "count",
|
||||
|
|
|
|||
|
|
@ -11,6 +11,8 @@ import com.pixelized.shared.lwa.model.inventory.factory.InventoryJsonFactory
|
|||
import com.pixelized.shared.lwa.model.inventory.factory.InventoryJsonFactoryV1
|
||||
import com.pixelized.shared.lwa.model.item.factory.ItemJsonFactory
|
||||
import com.pixelized.shared.lwa.model.item.factory.ItemJsonFactoryV1
|
||||
import com.pixelized.shared.lwa.model.map.factory.MapJsonFactory
|
||||
import com.pixelized.shared.lwa.model.map.factory.MapJsonFactoryV1
|
||||
import com.pixelized.shared.lwa.model.tag.TagJsonFactory
|
||||
import com.pixelized.shared.lwa.parser.dice.DiceParser
|
||||
import com.pixelized.shared.lwa.parser.expression.ExpressionParser
|
||||
|
|
@ -58,6 +60,8 @@ val factoryDependencies
|
|||
factoryOf(::ItemJsonFactoryV1)
|
||||
factoryOf(::InventoryJsonFactory)
|
||||
factoryOf(::InventoryJsonFactoryV1)
|
||||
factoryOf(::MapJsonFactory)
|
||||
factoryOf(::MapJsonFactoryV1)
|
||||
}
|
||||
|
||||
val parserDependencies
|
||||
|
|
|
|||
|
|
@ -1,13 +1,25 @@
|
|||
package com.pixelized.shared.lwa.model.campaign
|
||||
|
||||
import com.pixelized.shared.lwa.model.map.Map
|
||||
|
||||
data class Campaign(
|
||||
val characters: Set<String>,
|
||||
val npcs: Set<String>,
|
||||
val map: MiniMap?,
|
||||
val scene: Scene,
|
||||
val options: Options,
|
||||
) {
|
||||
val instances = characters + npcs
|
||||
|
||||
data class MiniMap(
|
||||
val id: String,
|
||||
val camera: Map.Camera?,
|
||||
) {
|
||||
companion object {
|
||||
fun empty(): MiniMap? = null
|
||||
}
|
||||
}
|
||||
|
||||
data class Scene(
|
||||
val name: String,
|
||||
) {
|
||||
|
|
@ -34,6 +46,7 @@ data class Campaign(
|
|||
fun empty() = Campaign(
|
||||
characters = emptySet(),
|
||||
npcs = emptySet(),
|
||||
map = null,
|
||||
scene = Scene.empty(),
|
||||
options = Options.empty(),
|
||||
)
|
||||
|
|
|
|||
|
|
@ -12,6 +12,9 @@ sealed interface CampaignJson {
|
|||
sealed interface CharacteristicJson
|
||||
}
|
||||
|
||||
@Serializable
|
||||
sealed interface MiniMap
|
||||
|
||||
@Serializable
|
||||
sealed interface SceneJson
|
||||
|
||||
|
|
|
|||
|
|
@ -6,10 +6,24 @@ import kotlinx.serialization.Serializable
|
|||
data class CampaignJsonV2(
|
||||
val characters: Set<String>,
|
||||
val npcs: Set<String>,
|
||||
val map: MiniMapJson?,
|
||||
val scene: SceneJsonV2?,
|
||||
val options: OptionsJsonV2?,
|
||||
) : CampaignJson {
|
||||
|
||||
@Serializable
|
||||
data class MiniMapJson(
|
||||
val id: String,
|
||||
val camera: CameraJson?,
|
||||
): CampaignJson.MiniMap {
|
||||
@Serializable
|
||||
data class CameraJson(
|
||||
val zoom: Float,
|
||||
val offsetX: Int,
|
||||
val offsetY: Int,
|
||||
)
|
||||
}
|
||||
|
||||
@Serializable
|
||||
data class SceneJsonV2(
|
||||
val name: String,
|
||||
|
|
|
|||
|
|
@ -27,6 +27,14 @@ class CampaignJsonFactory(
|
|||
}
|
||||
}
|
||||
|
||||
fun convertFromJson(
|
||||
json: CampaignJson.MiniMap
|
||||
): Campaign.MiniMap {
|
||||
return when (json) {
|
||||
is CampaignJsonV2.MiniMapJson -> v2.convertFromJson(mapJson = json)
|
||||
}
|
||||
}
|
||||
|
||||
// Json conversion.
|
||||
|
||||
fun createScene(
|
||||
|
|
@ -43,6 +51,18 @@ class CampaignJsonFactory(
|
|||
return CampaignJsonV2(
|
||||
characters = campaign.characters,
|
||||
npcs = campaign.npcs,
|
||||
map = campaign.map?.let { campaign ->
|
||||
CampaignJsonV2.MiniMapJson(
|
||||
id = campaign.id,
|
||||
camera = campaign.camera?.let { camera ->
|
||||
CampaignJsonV2.MiniMapJson.CameraJson(
|
||||
zoom = camera.zoom,
|
||||
offsetX = camera.offsetX,
|
||||
offsetY = camera.offsetY,
|
||||
)
|
||||
}
|
||||
)
|
||||
},
|
||||
scene = CampaignJsonV2.SceneJsonV2(
|
||||
name = campaign.scene.name,
|
||||
),
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ class CampaignJsonV1Factory {
|
|||
return Campaign(
|
||||
characters = emptySet(),
|
||||
npcs = emptySet(),
|
||||
map = null,
|
||||
scene = campaignJson.scene
|
||||
?.let { convertFromJson(it) }
|
||||
?: Campaign.Scene.empty(),
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ package com.pixelized.shared.lwa.model.campaign.factory
|
|||
|
||||
import com.pixelized.shared.lwa.model.campaign.Campaign
|
||||
import com.pixelized.shared.lwa.model.campaign.CampaignJsonV2
|
||||
import com.pixelized.shared.lwa.model.map.Map
|
||||
|
||||
class CampaignJsonV2Factory {
|
||||
|
||||
|
|
@ -11,15 +12,33 @@ class CampaignJsonV2Factory {
|
|||
return Campaign(
|
||||
characters = campaignJson.characters,
|
||||
npcs = campaignJson.npcs,
|
||||
map = campaignJson.map
|
||||
?.let { convertFromJson(mapJson = it) }
|
||||
?: Campaign.MiniMap.empty(),
|
||||
scene = campaignJson.scene
|
||||
?.let { convertFromJson(it) }
|
||||
?.let { convertFromJson(sceneJson = it) }
|
||||
?: Campaign.Scene.empty(),
|
||||
options = campaignJson.options
|
||||
?.let { convertFromJson(it) }
|
||||
?.let { convertFromJson(optionsJson = it) }
|
||||
?: Campaign.Options.empty()
|
||||
)
|
||||
}
|
||||
|
||||
fun convertFromJson(
|
||||
mapJson: CampaignJsonV2.MiniMapJson,
|
||||
): Campaign.MiniMap {
|
||||
return Campaign.MiniMap(
|
||||
id = mapJson.id,
|
||||
camera = mapJson.camera?.let {
|
||||
Map.Camera(
|
||||
zoom = it.zoom,
|
||||
offsetX = it.offsetX,
|
||||
offsetY = it.offsetY,
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
fun convertFromJson(
|
||||
sceneJson: CampaignJsonV2.SceneJsonV2,
|
||||
): Campaign.Scene {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,26 @@
|
|||
package com.pixelized.shared.lwa.model.map
|
||||
|
||||
data class Map(
|
||||
val id: String,
|
||||
val name : String,
|
||||
val camera: Camera,
|
||||
val size: Size,
|
||||
val resources: List<Resource>,
|
||||
) {
|
||||
data class Camera(
|
||||
val zoom: Float,
|
||||
val offsetX: Int,
|
||||
val offsetY: Int,
|
||||
)
|
||||
|
||||
data class Size(
|
||||
val width: Int,
|
||||
val height: Int,
|
||||
)
|
||||
|
||||
data class Resource(
|
||||
val id: String,
|
||||
val name: String,
|
||||
val uri: String,
|
||||
)
|
||||
}
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
package com.pixelized.shared.lwa.model.map
|
||||
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
sealed interface MapJson {
|
||||
val id: String
|
||||
}
|
||||
|
|
@ -0,0 +1,32 @@
|
|||
package com.pixelized.shared.lwa.model.map
|
||||
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
data class MapJsonV1(
|
||||
override val id: String,
|
||||
val name: String,
|
||||
val camera: Camera,
|
||||
val size: Size,
|
||||
val resources: List<Resource>,
|
||||
) : MapJson {
|
||||
@Serializable
|
||||
data class Camera(
|
||||
val zoom: Float,
|
||||
val offsetX: Int,
|
||||
val offsetY: Int,
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class Size(
|
||||
val width: Int,
|
||||
val height: Int,
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class Resource(
|
||||
val id: String,
|
||||
val name: String,
|
||||
val uri: String,
|
||||
)
|
||||
}
|
||||
|
|
@ -0,0 +1,43 @@
|
|||
package com.pixelized.shared.lwa.model.map.factory
|
||||
|
||||
import com.pixelized.shared.lwa.model.map.Map
|
||||
import com.pixelized.shared.lwa.model.map.MapJson
|
||||
import com.pixelized.shared.lwa.model.map.MapJsonV1
|
||||
|
||||
class MapJsonFactory(
|
||||
private val v1: MapJsonFactoryV1,
|
||||
) {
|
||||
|
||||
fun convertFromJson(
|
||||
json: MapJson,
|
||||
): Map {
|
||||
return when (json) {
|
||||
is MapJsonV1 -> v1.convertFromJson(json = json)
|
||||
}
|
||||
}
|
||||
|
||||
fun convertToJson(
|
||||
item: Map,
|
||||
): MapJson {
|
||||
return MapJsonV1(
|
||||
id = item.id,
|
||||
name = item.name,
|
||||
camera = MapJsonV1.Camera(
|
||||
zoom = item.camera.zoom,
|
||||
offsetX = item.camera.offsetX,
|
||||
offsetY = item.camera.offsetY,
|
||||
),
|
||||
size = MapJsonV1.Size(
|
||||
width = item.size.width,
|
||||
height = item.size.height,
|
||||
),
|
||||
resources = item.resources.map {
|
||||
MapJsonV1.Resource(
|
||||
id = it.id,
|
||||
name = it.name,
|
||||
uri = it.uri,
|
||||
)
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,31 @@
|
|||
package com.pixelized.shared.lwa.model.map.factory
|
||||
|
||||
import com.pixelized.shared.lwa.model.map.Map
|
||||
import com.pixelized.shared.lwa.model.map.MapJsonV1
|
||||
|
||||
class MapJsonFactoryV1 {
|
||||
fun convertFromJson(
|
||||
json: MapJsonV1,
|
||||
): Map {
|
||||
return Map(
|
||||
id = json.id,
|
||||
name = json.name,
|
||||
camera = Map.Camera(
|
||||
zoom = json.camera.zoom,
|
||||
offsetX = json.camera.offsetX,
|
||||
offsetY = json.camera.offsetY,
|
||||
),
|
||||
size = Map.Size(
|
||||
width = json.size.width,
|
||||
height = json.size.height,
|
||||
),
|
||||
resources = json.resources.map {
|
||||
Map.Resource(
|
||||
id = it.id,
|
||||
name = it.name,
|
||||
uri = it.uri,
|
||||
)
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -74,4 +74,19 @@ sealed interface ApiSynchronisation : SocketMessage {
|
|||
override val timestamp: Long,
|
||||
override val characterSheetId: String,
|
||||
) : InventoryApiSynchronisation
|
||||
|
||||
@Serializable
|
||||
sealed interface MapApiSynchronisation : ApiSynchronisation
|
||||
|
||||
@Serializable
|
||||
data class MapUpdate(
|
||||
override val timestamp: Long,
|
||||
val id: String,
|
||||
) : MapApiSynchronisation
|
||||
|
||||
@Serializable
|
||||
data class MapDelete(
|
||||
override val timestamp: Long,
|
||||
val id: String,
|
||||
) : MapApiSynchronisation
|
||||
}
|
||||
|
|
@ -1,5 +1,6 @@
|
|||
package com.pixelized.shared.lwa.protocol.websocket
|
||||
|
||||
import com.pixelized.shared.lwa.model.campaign.CampaignJson
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
|
|
@ -7,7 +8,7 @@ sealed interface CampaignEvent : SocketMessage {
|
|||
|
||||
// TODO Will probably be moved when the scene become more complex, (should already actually)
|
||||
@Serializable
|
||||
data class UpdateScene(
|
||||
data class SceneUpdated(
|
||||
override val timestamp: Long,
|
||||
val name: String,
|
||||
) : CampaignEvent
|
||||
|
|
@ -35,4 +36,15 @@ sealed interface CampaignEvent : SocketMessage {
|
|||
override val timestamp: Long,
|
||||
override val characterSheetId: String,
|
||||
) : CampaignEvent, CharacterSheetIdMessage
|
||||
|
||||
@Serializable
|
||||
data class MapUpdated(
|
||||
override val timestamp: Long,
|
||||
val map: CampaignJson.MiniMap,
|
||||
): CampaignEvent
|
||||
|
||||
@Serializable
|
||||
data class MapDeleted(
|
||||
override val timestamp: Long,
|
||||
): CampaignEvent
|
||||
}
|
||||
|
|
@ -83,4 +83,14 @@ class PathProvider(
|
|||
OperatingSystem.Macintosh -> "${storePath(os = os, app = app)}inventory/"
|
||||
}
|
||||
}
|
||||
|
||||
fun mapPath(
|
||||
os: OperatingSystem = this.operatingSystem,
|
||||
app: String = this.appName,
|
||||
): String {
|
||||
return when (os) {
|
||||
OperatingSystem.Windows -> "${storePath(os = os, app = app)}maps\\"
|
||||
OperatingSystem.Macintosh -> "${storePath(os = os, app = app)}maps/"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue