diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/repository/campaign/CampaignRepository.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/repository/campaign/CampaignRepository.kt index 7cd1b59..5d6303b 100644 --- a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/repository/campaign/CampaignRepository.kt +++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/repository/campaign/CampaignRepository.kt @@ -32,8 +32,8 @@ class CampaignRepository( } fun characterInstance( - chracterInstanceId: Campaign.CharacterInstance.Id, + characterInstanceId: Campaign.CharacterInstance.Id, ): Campaign.CharacterInstance { - return campaignFlow.value.character(chracterInstanceId) + return campaignFlow.value.character(characterInstanceId) } } \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/repository/campaign/CampaignStore.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/repository/campaign/CampaignStore.kt index ed78592..b337c1f 100644 --- a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/repository/campaign/CampaignStore.kt +++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/repository/campaign/CampaignStore.kt @@ -5,9 +5,10 @@ import com.pixelized.desktop.lwa.repository.network.NetworkRepository import com.pixelized.shared.lwa.model.campaign.Campaign import com.pixelized.shared.lwa.model.campaign.CampaignJsonFactory import com.pixelized.shared.lwa.model.campaign.character +import com.pixelized.shared.lwa.model.campaign.npc import com.pixelized.shared.lwa.protocol.websocket.Message +import com.pixelized.shared.lwa.protocol.websocket.payload.CampaignMessage import com.pixelized.shared.lwa.protocol.websocket.payload.RestSynchronisation -import com.pixelized.shared.lwa.protocol.websocket.payload.UpdatePlayerCharacteristicMessage import com.pixelized.shared.lwa.usecase.CampaignUseCase import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers @@ -46,21 +47,7 @@ class CampaignStore( return data } - private fun updateCharacteristic( - characterId: Campaign.CharacterInstance.Id, - characteristic: Campaign.CharacterInstance.Characteristic, - value: Int, - ) { - val campaign = _campaignFlow.value - val characters = campaign.characters.toMutableMap().also { - it[characterId] = useCase.updateCharacteristic( - character = campaign.character(id = characterId), - characteristic = characteristic, - value = value - ) - } - _campaignFlow.value = _campaignFlow.value.copy(characters = characters) - } + // region : WebSocket message Handling. private suspend fun handleMessage(message: Message) { when (val payload = message.value) { @@ -68,17 +55,89 @@ class CampaignStore( update() } - is UpdatePlayerCharacteristicMessage -> { - val id = factory.convertFromV1(characterInstanceIdJson = payload.characterInstanceId) - val characteristic = factory.convertFromV1(characteristicJson = payload.characteristic) - updateCharacteristic( - characterId = id, - characteristic = characteristic, - value = payload.value, + is CampaignMessage -> { + val instanceId = Campaign.CharacterInstance.Id( + characterSheetId = payload.characterSheetId, + instanceId = payload.instanceId, ) + when (payload) { + is CampaignMessage.UpdateCharacteristic -> updateCharacteristic( + characterInstanceId = instanceId, + characteristic = factory.convertFromV1(characteristicJson = payload.characteristic), + value = payload.value, + ) + + is CampaignMessage.UpdateDiminished -> updateDiminished( + characterInstanceId = instanceId, + diminished = payload.diminished, + ) + } } else -> Unit } } -} \ No newline at end of file + + private fun updateCharacteristic( + characterInstanceId: Campaign.CharacterInstance.Id, + characteristic: Campaign.CharacterInstance.Characteristic, + value: Int, + ) { + val campaign = _campaignFlow.value + + when { + campaign.characters.containsKey(characterInstanceId) -> { + val characters = campaign.characters.toMutableMap().also { + it[characterInstanceId] = useCase.updateCharacteristic( + instance = campaign.character(id = characterInstanceId), + characteristic = characteristic, + value = value + ) + } + _campaignFlow.value = _campaignFlow.value.copy(characters = characters) + } + + campaign.npcs.containsKey(characterInstanceId) -> { + val npcs = campaign.npcs.toMutableMap().also { + it[characterInstanceId] = useCase.updateCharacteristic( + instance = campaign.npc(id = characterInstanceId), + characteristic = characteristic, + value = value + ) + } + _campaignFlow.value = _campaignFlow.value.copy(npcs = npcs) + } + } + } + + private fun updateDiminished( + characterInstanceId: Campaign.CharacterInstance.Id, + diminished: Int, + ) { + val campaign = _campaignFlow.value + + when { + campaign.characters.containsKey(characterInstanceId) -> { + val characters = campaign.characters.toMutableMap().also { + it[characterInstanceId] = useCase.updateDiminished( + instance = campaign.character(id = characterInstanceId), + diminished = diminished, + ) + } + _campaignFlow.value = _campaignFlow.value.copy(characters = characters) + } + + campaign.npcs.containsKey(characterInstanceId) -> { + val npcs = campaign.npcs.toMutableMap().also { + it[characterInstanceId] = useCase.updateDiminished( + instance = campaign.npc(id = characterInstanceId), + diminished = diminished, + ) + } + _campaignFlow.value = _campaignFlow.value.copy(npcs = npcs) + } + } + } + + // endregion +} diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/composable/character/characteristic/CharacterDetailCharacteristicDialogViewModel.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/composable/character/characteristic/CharacterDetailCharacteristicDialogViewModel.kt index e9ba1dd..3836d53 100644 --- a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/composable/character/characteristic/CharacterDetailCharacteristicDialogViewModel.kt +++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/composable/character/characteristic/CharacterDetailCharacteristicDialogViewModel.kt @@ -13,7 +13,7 @@ import com.pixelized.shared.lwa.model.campaign.Campaign import com.pixelized.shared.lwa.model.campaign.Campaign.CharacterInstance.Characteristic import com.pixelized.shared.lwa.model.campaign.CampaignJsonFactory import com.pixelized.shared.lwa.model.characterSheet.CharacterSheet -import com.pixelized.shared.lwa.protocol.websocket.payload.UpdatePlayerCharacteristicMessage +import com.pixelized.shared.lwa.protocol.websocket.payload.CampaignMessage class CharacterDetailCharacteristicDialogViewModel( private val characterSheetRepository: CharacterSheetRepository, @@ -42,7 +42,7 @@ class CharacterDetailCharacteristicDialogViewModel( characterSheetId = characterInstanceId.characterSheetId, ) val characterInstance: Campaign.CharacterInstance = campaignRepository.characterInstance( - chracterInstanceId = characterInstanceId, + characterInstanceId = characterInstanceId, ) val alterations: Map> = alterationRepository.alterations( characterInstanceId = characterInstanceId, @@ -61,42 +61,33 @@ class CharacterDetailCharacteristicDialogViewModel( characteristic: Characteristic, value: Int, ) { - // fetch the linked character sheet + // fetch the linked character sheet, if null stop exit this function. val sheet = characterSheetRepository.characterDetail( characterSheetId = characterInstanceId.characterSheetId, - ) + ) ?: return val alterations = alterationRepository.alterations( characterInstanceId = characterInstanceId, ) // we need the maximum HP / Power that the character sheet have. - if (sheet != null) { - val alteredSheet = alteredCharacterSheetFactory.sheet( - characterSheet = sheet, - alterations = alterations, - ) - // convert the data to json format - val characterInstanceIdJson = campaignJsonFactory.convertToJson( - id = characterInstanceId, - ) - val characteristicJson = campaignJsonFactory.convertToJson( - characteristic = characteristic, - ) - // share the data through the websocket. - network.share( - payload = UpdatePlayerCharacteristicMessage( - characterInstanceId = characterInstanceIdJson, - characteristic = characteristicJson, - value = when (characteristic) { - Characteristic.Damage -> { - alteredSheet.maxHp - value - } - - Characteristic.Power -> { - alteredSheet.maxPp - value - } - }, - ), - ) - } + val alteredSheet = alteredCharacterSheetFactory.sheet( + characterSheet = sheet, + alterations = alterations, + ) + // convert the data to json format + val characteristicJson = campaignJsonFactory.convertToJson( + characteristic = characteristic, + ) + // share the data through the websocket. + network.share( + payload = CampaignMessage.UpdateCharacteristic( + characterSheetId = characterInstanceId.characterSheetId, + instanceId = characterInstanceId.instanceId, + characteristic = characteristicJson, + value = when (characteristic) { + Characteristic.Damage -> alteredSheet.maxHp - value + Characteristic.Power -> alteredSheet.maxPp - value + }, + ), + ) } } \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/campaign/CampaignScreen.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/campaign/CampaignScreen.kt index 407ea16..1879ee3 100644 --- a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/campaign/CampaignScreen.kt +++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/campaign/CampaignScreen.kt @@ -181,11 +181,13 @@ fun CampaignScreen( DiminishedStatDialog( dialog = dismissedViewModel.diminishedDialog, onConfirm = { diminished -> - dismissedViewModel.changeDiminished( - dialog = diminished - ) - blurController.hide() - dismissedViewModel.hideDiminishedDialog() + scope.launch { + dismissedViewModel.changeDiminished( + dialog = diminished + ) + blurController.hide() + dismissedViewModel.hideDiminishedDialog() + } }, onDismissRequest = { blurController.hide() diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/campaign/player/detail/CharacterDetailFactory.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/campaign/player/detail/CharacterDetailFactory.kt index f952a41..b4ae7fc 100644 --- a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/campaign/player/detail/CharacterDetailFactory.kt +++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/campaign/player/detail/CharacterDetailFactory.kt @@ -55,6 +55,7 @@ class CharacterDetailFactory( return CharacterDetailHeaderUio( id = characterInstanceId, portrait = alteredCharacterSheet.portrait, + diminished = characterInstance.diminished, name = alteredCharacterSheet.name, hp = "${maxHp - characterInstance.damage}", maxHp = "$maxHp", @@ -71,6 +72,7 @@ class CharacterDetailFactory( suspend fun convertToCharacterDetailSheetUio( characterInstanceId: Campaign.CharacterInstance.Id, characterSheet: CharacterSheet?, + characterInstance: Campaign.CharacterInstance, alterations: Map>, ): CharacterDetailSheetUio? { if (characterSheet == null) return null @@ -94,7 +96,7 @@ class CharacterDetailFactory( characterSheetId = characterSheet.id, label = getString(Res.string.character_sheet__characteristics__str), rollAction = "1d100", - rollSuccessValue = alteredCharacterSheet.strength * 5, + rollSuccessValue = alteredCharacterSheet.strength * 5 - characterInstance.diminished, ), ), CharacterDetailSheetCharacteristicUio( @@ -108,7 +110,7 @@ class CharacterDetailFactory( characterSheetId = characterSheet.id, label = getString(Res.string.character_sheet__characteristics__dex), rollAction = "1d100", - rollSuccessValue = alteredCharacterSheet.dexterity * 5, + rollSuccessValue = alteredCharacterSheet.dexterity * 5 - characterInstance.diminished, ), ), CharacterDetailSheetCharacteristicUio( @@ -122,7 +124,7 @@ class CharacterDetailFactory( characterSheetId = characterSheet.id, label = getString(Res.string.character_sheet__characteristics__con), rollAction = "1d100", - rollSuccessValue = alteredCharacterSheet.constitution * 5, + rollSuccessValue = alteredCharacterSheet.constitution * 5 - characterInstance.diminished, ), ), CharacterDetailSheetCharacteristicUio( @@ -136,7 +138,7 @@ class CharacterDetailFactory( characterSheetId = characterSheet.id, label = getString(Res.string.character_sheet__characteristics__hei), rollAction = "1d100", - rollSuccessValue = alteredCharacterSheet.height * 5, + rollSuccessValue = alteredCharacterSheet.height * 5 - characterInstance.diminished, ), ), CharacterDetailSheetCharacteristicUio( @@ -150,7 +152,7 @@ class CharacterDetailFactory( characterSheetId = characterSheet.id, label = getString(Res.string.character_sheet__characteristics__int), rollAction = "1d100", - rollSuccessValue = alteredCharacterSheet.intelligence * 5, + rollSuccessValue = alteredCharacterSheet.intelligence * 5 - characterInstance.diminished, ), ), CharacterDetailSheetCharacteristicUio( @@ -164,7 +166,7 @@ class CharacterDetailFactory( characterSheetId = characterSheet.id, label = getString(Res.string.character_sheet__characteristics__pow), rollAction = "1d100", - rollSuccessValue = alteredCharacterSheet.power * 5, + rollSuccessValue = alteredCharacterSheet.power * 5 - characterInstance.diminished, ), ), CharacterDetailSheetCharacteristicUio( @@ -178,7 +180,7 @@ class CharacterDetailFactory( characterSheetId = characterSheet.id, label = getString(Res.string.character_sheet__characteristics__cha), rollAction = "1d100", - rollSuccessValue = alteredCharacterSheet.charisma * 5, + rollSuccessValue = alteredCharacterSheet.charisma * 5 - characterInstance.diminished, ), ), ), @@ -186,6 +188,7 @@ class CharacterDetailFactory( val value = expressionUseCase.computeSkillValue( sheet = characterSheet, skill = skill, + diminished = characterInstance.diminished, alterations = alterations, ) CharacterDetailSheetSkillUio( @@ -212,6 +215,7 @@ class CharacterDetailFactory( val value = expressionUseCase.computeSkillValue( sheet = characterSheet, skill = skill, + diminished = characterInstance.diminished, alterations = alterations, ) CharacterDetailSheetSkillUio( @@ -238,6 +242,7 @@ class CharacterDetailFactory( val value = expressionUseCase.computeSkillValue( sheet = characterSheet, skill = skill, + diminished = characterInstance.diminished, alterations = alterations, ) CharacterDetailSheetSkillUio( diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/campaign/player/detail/CharacterDetailViewModel.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/campaign/player/detail/CharacterDetailViewModel.kt index 42e647a..f37871b 100644 --- a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/campaign/player/detail/CharacterDetailViewModel.kt +++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/campaign/player/detail/CharacterDetailViewModel.kt @@ -47,12 +47,14 @@ class CharacterDetailViewModel( initialValue = null, ), sheet = combine( + campaignRepository.characterInstanceFlow(id = characterInstanceId), characterSheetRepository.characterDetailFlow(characterId = characterInstanceId.characterSheetId), alterationRepository.alterationsFlow(characterId = characterInstanceId), - ) { characterSheet, alterations -> + ) { characterInstance, characterSheet, alterations -> characterDetailFactory.convertToCharacterDetailSheetUio( characterInstanceId = characterInstanceId, characterSheet = characterSheet, + characterInstance = characterInstance, alterations = alterations, ) }.stateIn( diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/campaign/player/detail/CharacterDiminishedViewModel.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/campaign/player/detail/CharacterDiminishedViewModel.kt index 0243695..8f99b27 100644 --- a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/campaign/player/detail/CharacterDiminishedViewModel.kt +++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/campaign/player/detail/CharacterDiminishedViewModel.kt @@ -5,31 +5,37 @@ import androidx.compose.runtime.mutableStateOf import androidx.compose.ui.text.TextRange import androidx.compose.ui.text.input.TextFieldValue import androidx.lifecycle.ViewModel -import com.pixelized.desktop.lwa.repository.characterSheet.CharacterSheetRepository +import com.pixelized.desktop.lwa.repository.campaign.CampaignRepository +import com.pixelized.desktop.lwa.repository.network.NetworkRepository import com.pixelized.desktop.lwa.ui.screen.characterSheet.detail.dialog.DiminishedStatDialogUio import com.pixelized.shared.lwa.model.campaign.Campaign +import com.pixelized.shared.lwa.protocol.websocket.payload.CampaignMessage import lwacharactersheet.composeapp.generated.resources.Res import lwacharactersheet.composeapp.generated.resources.character_sheet__diminished__label import org.jetbrains.compose.resources.getString class CharacterDiminishedViewModel( - private val characterSheetRepository: CharacterSheetRepository, + private val campaignRepository: CampaignRepository, + private val networkRepository: NetworkRepository, ) : ViewModel() { private val _diminishedDialog = mutableStateOf(null) val diminishedDialog: State get() = _diminishedDialog suspend fun showDiminishedDialog( - characterInstanceId: Campaign.CharacterInstance.Id?, + characterInstanceId: Campaign.CharacterInstance.Id, ) { - if (characterInstanceId == null) return - - val diminished = 0 // TODO repository.characterDiminishedFlow(id = id).value + val characterInstance = campaignRepository.characterInstance( + characterInstanceId = characterInstanceId, + ) val textFieldValue = mutableStateOf( - TextFieldValue("$diminished", selection = TextRange(index = 0)) + TextFieldValue( + text = "${characterInstance.diminished}", + selection = TextRange(index = 0), + ) ) _diminishedDialog.value = DiminishedStatDialogUio( - id = characterInstanceId, + characterInstanceId = characterInstanceId, label = getString(resource = Res.string.character_sheet__diminished__label), value = { textFieldValue.value }, onValueChange = { value -> @@ -45,12 +51,16 @@ class CharacterDiminishedViewModel( _diminishedDialog.value = null } - fun changeDiminished(dialog: DiminishedStatDialogUio) { - val value = dialog.value().text.toIntOrNull() ?: 0 - // TODO -// repository.setDiminishedForCharacter( -// id = dialog.id, -// diminished = value, -// ) + suspend fun changeDiminished( + dialog: DiminishedStatDialogUio, + ) { + val diminished = dialog.value().text.toIntOrNull() ?: 0 + networkRepository.share( + payload = CampaignMessage.UpdateDiminished( + characterSheetId = dialog.characterInstanceId.characterSheetId, + instanceId = dialog.characterInstanceId.instanceId, + diminished = diminished, + ) + ) } } \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/campaign/player/detail/header/CharacterDetailHeader.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/campaign/player/detail/header/CharacterDetailHeader.kt index 9d0940f..495d330 100644 --- a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/campaign/player/detail/header/CharacterDetailHeader.kt +++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/campaign/player/detail/header/CharacterDetailHeader.kt @@ -1,17 +1,28 @@ package com.pixelized.desktop.lwa.ui.screen.campaign.player.detail.header +import androidx.compose.animation.AnimatedContent +import androidx.compose.animation.SizeTransform +import androidx.compose.animation.fadeIn +import androidx.compose.animation.fadeOut +import androidx.compose.animation.slideInVertically +import androidx.compose.animation.slideOutVertically +import androidx.compose.animation.togetherWith import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.width import androidx.compose.foundation.shape.CircleShape import androidx.compose.material.Icon import androidx.compose.material.IconButton import androidx.compose.material.MaterialTheme import androidx.compose.material.Text +import androidx.compose.material.minimumInteractiveComponentSize import androidx.compose.runtime.Composable import androidx.compose.runtime.Stable import androidx.compose.runtime.State @@ -19,6 +30,7 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp @@ -40,6 +52,7 @@ import org.jetbrains.compose.resources.painterResource data class CharacterDetailHeaderUio( val id: Campaign.CharacterInstance.Id, val portrait: String?, + val diminished: Int, val name: String, val hp: String, val maxHp: String, @@ -74,15 +87,35 @@ fun CharacterDetailHeader( overflow = TextOverflow.Ellipsis, maxLines = 1, ) - IconButton( - onClick = onDiminished, - ) { - Icon( - modifier = Modifier.size(size = 24.dp), - painter = painterResource(Res.drawable.ic_skull_24dp), - tint = MaterialTheme.colors.primary, - contentDescription = null, - ) + Box { + IconButton( + onClick = onDiminished, + ) { + Icon( + modifier = Modifier.size(size = 24.dp), + painter = painterResource(Res.drawable.ic_skull_24dp), + tint = MaterialTheme.colors.primary, + contentDescription = null, + ) + } + AnimatedContent( + modifier = Modifier.align(alignment = Alignment.BottomCenter), + targetState = header.value?.diminished, + transitionSpec = { + val sign = if ((targetState ?: 0) > (initialState ?: 0)) 1 else -1 + val enter = fadeIn() + slideInVertically { 16 * sign } + val exit = fadeOut() + slideOutVertically { -16 * sign } + enter togetherWith exit using SizeTransform(clip = false) + } + ) { + Text( + modifier = Modifier.width(width = 48.dp), + style = MaterialTheme.typography.caption, + color = MaterialTheme.colors.onSurface, + textAlign = TextAlign.Center, + text = "${it?.takeIf { it > 0 } ?: " "}", + ) + } } IconButton( onClick = onDismissRequest, diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/characterSheet/detail/CharacterSheetFactory.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/characterSheet/detail/CharacterSheetFactory.kt index e2a184f..1e2916e 100644 --- a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/characterSheet/detail/CharacterSheetFactory.kt +++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/characterSheet/detail/CharacterSheetFactory.kt @@ -216,6 +216,7 @@ class CharacterSheetFactory( value = skillUseCase.computeSkillValue( sheet = characterSheet, skill = skill, + diminished = instance.diminished, alterations = alterations, ), tooltips = skill.description?.let { @@ -240,6 +241,7 @@ class CharacterSheetFactory( value = skillUseCase.computeSkillValue( sheet = characterSheet, skill = skill, + diminished = instance.diminished, alterations = alterations, ), used = skill.used, @@ -258,6 +260,7 @@ class CharacterSheetFactory( value = skillUseCase.computeSkillValue( sheet = characterSheet, skill = skill, + diminished = instance.diminished, alterations = alterations, ), used = skill.used, diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/characterSheet/detail/dialog/DiminishedStatDialog.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/characterSheet/detail/dialog/DiminishedStatDialog.kt index cee44be..a00c0b6 100644 --- a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/characterSheet/detail/dialog/DiminishedStatDialog.kt +++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/characterSheet/detail/dialog/DiminishedStatDialog.kt @@ -50,7 +50,7 @@ import org.jetbrains.compose.resources.stringResource @Stable data class DiminishedStatDialogUio( - val id: Campaign.CharacterInstance.Id, + val characterInstanceId: Campaign.CharacterInstance.Id, val label: String, val value: () -> TextFieldValue, val onValueChange: (TextFieldValue) -> Unit, diff --git a/server/src/main/kotlin/com/pixelized/server/lwa/model/campaign/CampaignService.kt b/server/src/main/kotlin/com/pixelized/server/lwa/model/campaign/CampaignService.kt index 1475c85..50cb5ae 100644 --- a/server/src/main/kotlin/com/pixelized/server/lwa/model/campaign/CampaignService.kt +++ b/server/src/main/kotlin/com/pixelized/server/lwa/model/campaign/CampaignService.kt @@ -103,41 +103,77 @@ class CampaignService( // Data manipulation through WebSocket. - suspend fun updateCharacterCharacteristic( + suspend fun updateCharacteristic( characterInstanceId: Campaign.CharacterInstance.Id, characteristic: Campaign.CharacterInstance.Characteristic, value: Int, ) { - // fetch all the current campaign character - val characters = campaign.characters.toMutableMap() - // update the corresponding character using the use case. - characters[characterInstanceId] = useCase.updateCharacteristic( - character = campaign.character(id = characterInstanceId), - characteristic = characteristic, - value = value, - ) - // save the campaign to the disk + update the flow. - store.save( - campaign = campaign.copy(characters = characters) - ) + when { + campaign.characters.containsKey(characterInstanceId) -> { + // fetch all the current campaign character + val characters = campaign.characters.toMutableMap() + // update the corresponding character using the use case. + characters[characterInstanceId] = useCase.updateCharacteristic( + instance = campaign.character(id = characterInstanceId), + characteristic = characteristic, + value = value, + ) + // save the campaign to the disk + update the flow. + store.save( + campaign = campaign.copy(characters = characters) + ) + } + + campaign.npcs.containsKey(characterInstanceId) -> { + // fetch all the current campaign character + val npcs = campaign.npcs.toMutableMap() + // update the corresponding character using the use case. + npcs[characterInstanceId] = useCase.updateCharacteristic( + instance = campaign.npc(id = characterInstanceId), + characteristic = characteristic, + value = value, + ) + // save the campaign to the disk + update the flow. + store.save( + campaign = campaign.copy(npcs = npcs) + ) + } + } } - suspend fun updateNpcCharacteristic( - npcInstanceId: Campaign.CharacterInstance.Id, - characteristic: Campaign.CharacterInstance.Characteristic, - value: Int, + suspend fun updateDiminished( + characterInstanceId: Campaign.CharacterInstance.Id, + diminished: Int, ) { - // fetch all the current campaign character - val npcs = campaign.npcs.toMutableMap() - // update the corresponding character using the use case. - npcs[npcInstanceId] = useCase.updateCharacteristic( - character = campaign.npc(id = npcInstanceId), - characteristic = characteristic, - value = value, - ) - // save the campaign to the disk + update the flow. - store.save( - campaign = campaign.copy(npcs = npcs) - ) + when { + campaign.characters.containsKey(characterInstanceId) -> { + // fetch all the current campaign character + val characters = campaign.characters.toMutableMap() + // update the corresponding character using the use case. + characters[characterInstanceId] = useCase.updateDiminished( + instance = campaign.character(id = characterInstanceId), + diminished = diminished, + ) + // save the campaign to the disk + update the flow. + store.save( + campaign = campaign.copy(characters = characters) + ) + } + + campaign.npcs.containsKey(characterInstanceId) -> { + // fetch all the current campaign character + val npcs = campaign.npcs.toMutableMap() + // update the corresponding character using the use case. + npcs[characterInstanceId] = useCase.updateDiminished( + instance = campaign.npc(id = characterInstanceId), + diminished = diminished, + ) + // save the campaign to the disk + update the flow. + store.save( + campaign = campaign.copy(npcs = npcs) + ) + } + } + } } \ No newline at end of file diff --git a/server/src/main/kotlin/com/pixelized/server/lwa/model/campaign/CampaignStore.kt b/server/src/main/kotlin/com/pixelized/server/lwa/model/campaign/CampaignStore.kt index cf41cd0..297bc70 100644 --- a/server/src/main/kotlin/com/pixelized/server/lwa/model/campaign/CampaignStore.kt +++ b/server/src/main/kotlin/com/pixelized/server/lwa/model/campaign/CampaignStore.kt @@ -10,7 +10,6 @@ import kotlinx.coroutines.Job import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.launch -import kotlinx.serialization.encodeToString import kotlinx.serialization.json.Json import java.io.File @@ -18,7 +17,6 @@ class CampaignStore( private val factory: CampaignJsonFactory, private val json: Json, ) { - private val flow = MutableStateFlow(value = Campaign.EMPTY) init { diff --git a/server/src/main/kotlin/com/pixelized/server/lwa/server/Engine.kt b/server/src/main/kotlin/com/pixelized/server/lwa/server/Engine.kt index 1caeb13..358c028 100644 --- a/server/src/main/kotlin/com/pixelized/server/lwa/server/Engine.kt +++ b/server/src/main/kotlin/com/pixelized/server/lwa/server/Engine.kt @@ -3,11 +3,12 @@ package com.pixelized.server.lwa.server import com.pixelized.server.lwa.model.alteration.AlterationService import com.pixelized.server.lwa.model.campaign.CampaignService import com.pixelized.server.lwa.model.character.CharacterSheetService +import com.pixelized.shared.lwa.model.campaign.Campaign import com.pixelized.shared.lwa.model.campaign.CampaignJsonFactory import com.pixelized.shared.lwa.protocol.websocket.Message +import com.pixelized.shared.lwa.protocol.websocket.payload.CampaignMessage import com.pixelized.shared.lwa.protocol.websocket.payload.RestSynchronisation import com.pixelized.shared.lwa.protocol.websocket.payload.RollMessage -import com.pixelized.shared.lwa.protocol.websocket.payload.UpdatePlayerCharacteristicMessage import com.pixelized.shared.lwa.protocol.websocket.payload.UpdateSkillUsageMessage import kotlinx.coroutines.flow.MutableSharedFlow @@ -24,12 +25,23 @@ class Engine( is RollMessage -> Unit // Nothing to do here. - is UpdatePlayerCharacteristicMessage -> { - campaignService.updateCharacterCharacteristic( - characterInstanceId = campaignJsonFactory.convertFromV1(characterInstanceIdJson = data.characterInstanceId), - characteristic = campaignJsonFactory.convertFromV1(characteristicJson = data.characteristic), - value = data.value, + is CampaignMessage -> { + val instanceId = Campaign.CharacterInstance.Id( + characterSheetId = data.characterSheetId, + instanceId = data.instanceId, ) + when (data) { + is CampaignMessage.UpdateCharacteristic -> campaignService.updateCharacteristic( + characterInstanceId = instanceId, + characteristic = campaignJsonFactory.convertFromV1(characteristicJson = data.characteristic), + value = data.value, + ) + + is CampaignMessage.UpdateDiminished -> campaignService.updateDiminished( + characterInstanceId = instanceId, + diminished = data.diminished, + ) + } } is UpdateSkillUsageMessage -> characterService.updateCharacterSkillUsage( diff --git a/shared/src/commonMain/kotlin/com/pixelized/shared/lwa/model/campaign/Campaign.kt b/shared/src/commonMain/kotlin/com/pixelized/shared/lwa/model/campaign/Campaign.kt index 8ac614d..e95aef0 100644 --- a/shared/src/commonMain/kotlin/com/pixelized/shared/lwa/model/campaign/Campaign.kt +++ b/shared/src/commonMain/kotlin/com/pixelized/shared/lwa/model/campaign/Campaign.kt @@ -6,6 +6,7 @@ data class Campaign( ) { data class CharacterInstance( val characteristic: Map, + val diminished: Int, ) { data class Id( val characterSheetId: String, @@ -16,6 +17,13 @@ data class Campaign( Damage, Power, } + + companion object { + fun empty() = CharacterInstance( + characteristic = emptyMap(), + diminished = 0, + ) + } } companion object { @@ -27,15 +35,11 @@ data class Campaign( } fun Campaign.character(id: Campaign.CharacterInstance.Id): Campaign.CharacterInstance { - return characters[id] ?: Campaign.CharacterInstance( - characteristic = emptyMap(), - ) + return characters[id] ?: Campaign.CharacterInstance.empty() } fun Campaign.npc(id: Campaign.CharacterInstance.Id): Campaign.CharacterInstance { - return npcs[id] ?: Campaign.CharacterInstance( - characteristic = emptyMap(), - ) + return npcs[id] ?: Campaign.CharacterInstance.empty() } val Campaign.CharacterInstance.level diff --git a/shared/src/commonMain/kotlin/com/pixelized/shared/lwa/model/campaign/CampaignJsonFactory.kt b/shared/src/commonMain/kotlin/com/pixelized/shared/lwa/model/campaign/CampaignJsonFactory.kt index 84b3e66..d6b9f81 100644 --- a/shared/src/commonMain/kotlin/com/pixelized/shared/lwa/model/campaign/CampaignJsonFactory.kt +++ b/shared/src/commonMain/kotlin/com/pixelized/shared/lwa/model/campaign/CampaignJsonFactory.kt @@ -47,6 +47,7 @@ class CampaignJsonFactory { characteristic = characterInstanceJson.characteristic .map { char -> convertFromV1(characteristicJson = char.key) to char.value } .toMap(), + diminished = characterInstanceJson.diminished ?: 0, ) } @@ -85,6 +86,7 @@ class CampaignJsonFactory { characteristic = data.characteristic .map { char -> convertToJson(characteristic = char.key) to char.value } .toMap(), + diminished = data.diminished, ) } diff --git a/shared/src/commonMain/kotlin/com/pixelized/shared/lwa/model/campaign/CampaignJsonV1.kt b/shared/src/commonMain/kotlin/com/pixelized/shared/lwa/model/campaign/CampaignJsonV1.kt index d9fa986..64deba9 100644 --- a/shared/src/commonMain/kotlin/com/pixelized/shared/lwa/model/campaign/CampaignJsonV1.kt +++ b/shared/src/commonMain/kotlin/com/pixelized/shared/lwa/model/campaign/CampaignJsonV1.kt @@ -11,6 +11,7 @@ data class CampaignJsonV1( @Serializable data class CharacterInstanceJson( val characteristic: Map, + val diminished: Int?, ) { enum class Characteristic { Damage, diff --git a/shared/src/commonMain/kotlin/com/pixelized/shared/lwa/protocol/websocket/payload/CampaignMessage.kt b/shared/src/commonMain/kotlin/com/pixelized/shared/lwa/protocol/websocket/payload/CampaignMessage.kt new file mode 100644 index 0000000..db3090d --- /dev/null +++ b/shared/src/commonMain/kotlin/com/pixelized/shared/lwa/protocol/websocket/payload/CampaignMessage.kt @@ -0,0 +1,25 @@ +package com.pixelized.shared.lwa.protocol.websocket.payload + +import com.pixelized.shared.lwa.model.campaign.CampaignJsonV1 +import kotlinx.serialization.Serializable + +@Serializable +sealed interface CampaignMessage : MessagePayload { + val characterSheetId: String + val instanceId: Int + + @Serializable + data class UpdateCharacteristic( + override val characterSheetId: String, + override val instanceId: Int, + val characteristic: CampaignJsonV1.CharacterInstanceJson.Characteristic, + val value: Int, + ) : CampaignMessage + + @Serializable + data class UpdateDiminished( + override val characterSheetId: String, + override val instanceId: Int, + val diminished: Int, + ) : CampaignMessage +} \ No newline at end of file diff --git a/shared/src/commonMain/kotlin/com/pixelized/shared/lwa/protocol/websocket/payload/UpdatePlayerCharacteristicMessage.kt b/shared/src/commonMain/kotlin/com/pixelized/shared/lwa/protocol/websocket/payload/UpdatePlayerCharacteristicMessage.kt deleted file mode 100644 index 50a9f00..0000000 --- a/shared/src/commonMain/kotlin/com/pixelized/shared/lwa/protocol/websocket/payload/UpdatePlayerCharacteristicMessage.kt +++ /dev/null @@ -1,11 +0,0 @@ -package com.pixelized.shared.lwa.protocol.websocket.payload - -import com.pixelized.shared.lwa.model.campaign.CampaignJsonV1 -import kotlinx.serialization.Serializable - -@Serializable -data class UpdatePlayerCharacteristicMessage( - val characterInstanceId: String, - val characteristic: CampaignJsonV1.CharacterInstanceJson.Characteristic, - val value: Int, -) : MessagePayload \ No newline at end of file diff --git a/shared/src/commonMain/kotlin/com/pixelized/shared/lwa/usecase/CampaignUseCase.kt b/shared/src/commonMain/kotlin/com/pixelized/shared/lwa/usecase/CampaignUseCase.kt index 9f44153..5d172a9 100644 --- a/shared/src/commonMain/kotlin/com/pixelized/shared/lwa/usecase/CampaignUseCase.kt +++ b/shared/src/commonMain/kotlin/com/pixelized/shared/lwa/usecase/CampaignUseCase.kt @@ -5,14 +5,23 @@ import com.pixelized.shared.lwa.model.campaign.Campaign class CampaignUseCase { fun updateCharacteristic( - character: Campaign.CharacterInstance, + instance: Campaign.CharacterInstance, characteristic: Campaign.CharacterInstance.Characteristic, value: Int, ): Campaign.CharacterInstance { - return character.copy( - characteristic = character.characteristic.toMutableMap().also { + return instance.copy( + characteristic = instance.characteristic.toMutableMap().also { it[characteristic] = value } ) } + + fun updateDiminished( + instance: Campaign.CharacterInstance, + diminished: Int, + ): Campaign.CharacterInstance { + return instance.copy( + diminished = diminished, + ) + } } \ No newline at end of file diff --git a/shared/src/commonMain/kotlin/com/pixelized/shared/lwa/usecase/ExpressionUseCase.kt b/shared/src/commonMain/kotlin/com/pixelized/shared/lwa/usecase/ExpressionUseCase.kt index 0589a33..2f490d3 100644 --- a/shared/src/commonMain/kotlin/com/pixelized/shared/lwa/usecase/ExpressionUseCase.kt +++ b/shared/src/commonMain/kotlin/com/pixelized/shared/lwa/usecase/ExpressionUseCase.kt @@ -24,6 +24,7 @@ class ExpressionUseCase( fun computeSkillValue( sheet: CharacterSheet, alterations: Map>, + diminished: Int, skill: CharacterSheet.Skill, ): Int { val context = Context( @@ -51,7 +52,7 @@ class ExpressionUseCase( context.evaluate(it.expression) } ?: 0 - return max(base + bonus + level + alteration, 0) + return max(base + bonus + level + alteration - diminished, 0) } fun computeRoll(