Add support for the diminished state.

This commit is contained in:
Thomas Andres Gomez 2025-02-27 16:38:08 +01:00
parent 1fe75062b7
commit d8ce46fe43
20 changed files with 337 additions and 155 deletions

View file

@ -32,8 +32,8 @@ class CampaignRepository(
} }
fun characterInstance( fun characterInstance(
chracterInstanceId: Campaign.CharacterInstance.Id, characterInstanceId: Campaign.CharacterInstance.Id,
): Campaign.CharacterInstance { ): Campaign.CharacterInstance {
return campaignFlow.value.character(chracterInstanceId) return campaignFlow.value.character(characterInstanceId)
} }
} }

View file

@ -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.Campaign
import com.pixelized.shared.lwa.model.campaign.CampaignJsonFactory import com.pixelized.shared.lwa.model.campaign.CampaignJsonFactory
import com.pixelized.shared.lwa.model.campaign.character 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.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.RestSynchronisation
import com.pixelized.shared.lwa.protocol.websocket.payload.UpdatePlayerCharacteristicMessage
import com.pixelized.shared.lwa.usecase.CampaignUseCase import com.pixelized.shared.lwa.usecase.CampaignUseCase
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
@ -46,21 +47,7 @@ class CampaignStore(
return data return data
} }
private fun updateCharacteristic( // region : WebSocket message Handling.
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)
}
private suspend fun handleMessage(message: Message) { private suspend fun handleMessage(message: Message) {
when (val payload = message.value) { when (val payload = message.value) {
@ -68,17 +55,89 @@ class CampaignStore(
update() update()
} }
is UpdatePlayerCharacteristicMessage -> { is CampaignMessage -> {
val id = factory.convertFromV1(characterInstanceIdJson = payload.characterInstanceId) val instanceId = Campaign.CharacterInstance.Id(
val characteristic = factory.convertFromV1(characteristicJson = payload.characteristic) characterSheetId = payload.characterSheetId,
updateCharacteristic( instanceId = payload.instanceId,
characterId = id,
characteristic = characteristic,
value = payload.value,
) )
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 else -> Unit
} }
} }
}
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
}

View file

@ -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.Campaign.CharacterInstance.Characteristic
import com.pixelized.shared.lwa.model.campaign.CampaignJsonFactory import com.pixelized.shared.lwa.model.campaign.CampaignJsonFactory
import com.pixelized.shared.lwa.model.characterSheet.CharacterSheet 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( class CharacterDetailCharacteristicDialogViewModel(
private val characterSheetRepository: CharacterSheetRepository, private val characterSheetRepository: CharacterSheetRepository,
@ -42,7 +42,7 @@ class CharacterDetailCharacteristicDialogViewModel(
characterSheetId = characterInstanceId.characterSheetId, characterSheetId = characterInstanceId.characterSheetId,
) )
val characterInstance: Campaign.CharacterInstance = campaignRepository.characterInstance( val characterInstance: Campaign.CharacterInstance = campaignRepository.characterInstance(
chracterInstanceId = characterInstanceId, characterInstanceId = characterInstanceId,
) )
val alterations: Map<String, List<FieldAlteration>> = alterationRepository.alterations( val alterations: Map<String, List<FieldAlteration>> = alterationRepository.alterations(
characterInstanceId = characterInstanceId, characterInstanceId = characterInstanceId,
@ -61,42 +61,33 @@ class CharacterDetailCharacteristicDialogViewModel(
characteristic: Characteristic, characteristic: Characteristic,
value: Int, value: Int,
) { ) {
// fetch the linked character sheet // fetch the linked character sheet, if null stop exit this function.
val sheet = characterSheetRepository.characterDetail( val sheet = characterSheetRepository.characterDetail(
characterSheetId = characterInstanceId.characterSheetId, characterSheetId = characterInstanceId.characterSheetId,
) ) ?: return
val alterations = alterationRepository.alterations( val alterations = alterationRepository.alterations(
characterInstanceId = characterInstanceId, characterInstanceId = characterInstanceId,
) )
// we need the maximum HP / Power that the character sheet have. // we need the maximum HP / Power that the character sheet have.
if (sheet != null) { val alteredSheet = alteredCharacterSheetFactory.sheet(
val alteredSheet = alteredCharacterSheetFactory.sheet( characterSheet = sheet,
characterSheet = sheet, alterations = alterations,
alterations = alterations, )
) // convert the data to json format
// convert the data to json format val characteristicJson = campaignJsonFactory.convertToJson(
val characterInstanceIdJson = campaignJsonFactory.convertToJson( characteristic = characteristic,
id = characterInstanceId, )
) // share the data through the websocket.
val characteristicJson = campaignJsonFactory.convertToJson( network.share(
characteristic = characteristic, payload = CampaignMessage.UpdateCharacteristic(
) characterSheetId = characterInstanceId.characterSheetId,
// share the data through the websocket. instanceId = characterInstanceId.instanceId,
network.share( characteristic = characteristicJson,
payload = UpdatePlayerCharacteristicMessage( value = when (characteristic) {
characterInstanceId = characterInstanceIdJson, Characteristic.Damage -> alteredSheet.maxHp - value
characteristic = characteristicJson, Characteristic.Power -> alteredSheet.maxPp - value
value = when (characteristic) { },
Characteristic.Damage -> { ),
alteredSheet.maxHp - value )
}
Characteristic.Power -> {
alteredSheet.maxPp - value
}
},
),
)
}
} }
} }

View file

@ -181,11 +181,13 @@ fun CampaignScreen(
DiminishedStatDialog( DiminishedStatDialog(
dialog = dismissedViewModel.diminishedDialog, dialog = dismissedViewModel.diminishedDialog,
onConfirm = { diminished -> onConfirm = { diminished ->
dismissedViewModel.changeDiminished( scope.launch {
dialog = diminished dismissedViewModel.changeDiminished(
) dialog = diminished
blurController.hide() )
dismissedViewModel.hideDiminishedDialog() blurController.hide()
dismissedViewModel.hideDiminishedDialog()
}
}, },
onDismissRequest = { onDismissRequest = {
blurController.hide() blurController.hide()

View file

@ -55,6 +55,7 @@ class CharacterDetailFactory(
return CharacterDetailHeaderUio( return CharacterDetailHeaderUio(
id = characterInstanceId, id = characterInstanceId,
portrait = alteredCharacterSheet.portrait, portrait = alteredCharacterSheet.portrait,
diminished = characterInstance.diminished,
name = alteredCharacterSheet.name, name = alteredCharacterSheet.name,
hp = "${maxHp - characterInstance.damage}", hp = "${maxHp - characterInstance.damage}",
maxHp = "$maxHp", maxHp = "$maxHp",
@ -71,6 +72,7 @@ class CharacterDetailFactory(
suspend fun convertToCharacterDetailSheetUio( suspend fun convertToCharacterDetailSheetUio(
characterInstanceId: Campaign.CharacterInstance.Id, characterInstanceId: Campaign.CharacterInstance.Id,
characterSheet: CharacterSheet?, characterSheet: CharacterSheet?,
characterInstance: Campaign.CharacterInstance,
alterations: Map<String, List<FieldAlteration>>, alterations: Map<String, List<FieldAlteration>>,
): CharacterDetailSheetUio? { ): CharacterDetailSheetUio? {
if (characterSheet == null) return null if (characterSheet == null) return null
@ -94,7 +96,7 @@ class CharacterDetailFactory(
characterSheetId = characterSheet.id, characterSheetId = characterSheet.id,
label = getString(Res.string.character_sheet__characteristics__str), label = getString(Res.string.character_sheet__characteristics__str),
rollAction = "1d100", rollAction = "1d100",
rollSuccessValue = alteredCharacterSheet.strength * 5, rollSuccessValue = alteredCharacterSheet.strength * 5 - characterInstance.diminished,
), ),
), ),
CharacterDetailSheetCharacteristicUio( CharacterDetailSheetCharacteristicUio(
@ -108,7 +110,7 @@ class CharacterDetailFactory(
characterSheetId = characterSheet.id, characterSheetId = characterSheet.id,
label = getString(Res.string.character_sheet__characteristics__dex), label = getString(Res.string.character_sheet__characteristics__dex),
rollAction = "1d100", rollAction = "1d100",
rollSuccessValue = alteredCharacterSheet.dexterity * 5, rollSuccessValue = alteredCharacterSheet.dexterity * 5 - characterInstance.diminished,
), ),
), ),
CharacterDetailSheetCharacteristicUio( CharacterDetailSheetCharacteristicUio(
@ -122,7 +124,7 @@ class CharacterDetailFactory(
characterSheetId = characterSheet.id, characterSheetId = characterSheet.id,
label = getString(Res.string.character_sheet__characteristics__con), label = getString(Res.string.character_sheet__characteristics__con),
rollAction = "1d100", rollAction = "1d100",
rollSuccessValue = alteredCharacterSheet.constitution * 5, rollSuccessValue = alteredCharacterSheet.constitution * 5 - characterInstance.diminished,
), ),
), ),
CharacterDetailSheetCharacteristicUio( CharacterDetailSheetCharacteristicUio(
@ -136,7 +138,7 @@ class CharacterDetailFactory(
characterSheetId = characterSheet.id, characterSheetId = characterSheet.id,
label = getString(Res.string.character_sheet__characteristics__hei), label = getString(Res.string.character_sheet__characteristics__hei),
rollAction = "1d100", rollAction = "1d100",
rollSuccessValue = alteredCharacterSheet.height * 5, rollSuccessValue = alteredCharacterSheet.height * 5 - characterInstance.diminished,
), ),
), ),
CharacterDetailSheetCharacteristicUio( CharacterDetailSheetCharacteristicUio(
@ -150,7 +152,7 @@ class CharacterDetailFactory(
characterSheetId = characterSheet.id, characterSheetId = characterSheet.id,
label = getString(Res.string.character_sheet__characteristics__int), label = getString(Res.string.character_sheet__characteristics__int),
rollAction = "1d100", rollAction = "1d100",
rollSuccessValue = alteredCharacterSheet.intelligence * 5, rollSuccessValue = alteredCharacterSheet.intelligence * 5 - characterInstance.diminished,
), ),
), ),
CharacterDetailSheetCharacteristicUio( CharacterDetailSheetCharacteristicUio(
@ -164,7 +166,7 @@ class CharacterDetailFactory(
characterSheetId = characterSheet.id, characterSheetId = characterSheet.id,
label = getString(Res.string.character_sheet__characteristics__pow), label = getString(Res.string.character_sheet__characteristics__pow),
rollAction = "1d100", rollAction = "1d100",
rollSuccessValue = alteredCharacterSheet.power * 5, rollSuccessValue = alteredCharacterSheet.power * 5 - characterInstance.diminished,
), ),
), ),
CharacterDetailSheetCharacteristicUio( CharacterDetailSheetCharacteristicUio(
@ -178,7 +180,7 @@ class CharacterDetailFactory(
characterSheetId = characterSheet.id, characterSheetId = characterSheet.id,
label = getString(Res.string.character_sheet__characteristics__cha), label = getString(Res.string.character_sheet__characteristics__cha),
rollAction = "1d100", rollAction = "1d100",
rollSuccessValue = alteredCharacterSheet.charisma * 5, rollSuccessValue = alteredCharacterSheet.charisma * 5 - characterInstance.diminished,
), ),
), ),
), ),
@ -186,6 +188,7 @@ class CharacterDetailFactory(
val value = expressionUseCase.computeSkillValue( val value = expressionUseCase.computeSkillValue(
sheet = characterSheet, sheet = characterSheet,
skill = skill, skill = skill,
diminished = characterInstance.diminished,
alterations = alterations, alterations = alterations,
) )
CharacterDetailSheetSkillUio( CharacterDetailSheetSkillUio(
@ -212,6 +215,7 @@ class CharacterDetailFactory(
val value = expressionUseCase.computeSkillValue( val value = expressionUseCase.computeSkillValue(
sheet = characterSheet, sheet = characterSheet,
skill = skill, skill = skill,
diminished = characterInstance.diminished,
alterations = alterations, alterations = alterations,
) )
CharacterDetailSheetSkillUio( CharacterDetailSheetSkillUio(
@ -238,6 +242,7 @@ class CharacterDetailFactory(
val value = expressionUseCase.computeSkillValue( val value = expressionUseCase.computeSkillValue(
sheet = characterSheet, sheet = characterSheet,
skill = skill, skill = skill,
diminished = characterInstance.diminished,
alterations = alterations, alterations = alterations,
) )
CharacterDetailSheetSkillUio( CharacterDetailSheetSkillUio(

View file

@ -47,12 +47,14 @@ class CharacterDetailViewModel(
initialValue = null, initialValue = null,
), ),
sheet = combine( sheet = combine(
campaignRepository.characterInstanceFlow(id = characterInstanceId),
characterSheetRepository.characterDetailFlow(characterId = characterInstanceId.characterSheetId), characterSheetRepository.characterDetailFlow(characterId = characterInstanceId.characterSheetId),
alterationRepository.alterationsFlow(characterId = characterInstanceId), alterationRepository.alterationsFlow(characterId = characterInstanceId),
) { characterSheet, alterations -> ) { characterInstance, characterSheet, alterations ->
characterDetailFactory.convertToCharacterDetailSheetUio( characterDetailFactory.convertToCharacterDetailSheetUio(
characterInstanceId = characterInstanceId, characterInstanceId = characterInstanceId,
characterSheet = characterSheet, characterSheet = characterSheet,
characterInstance = characterInstance,
alterations = alterations, alterations = alterations,
) )
}.stateIn( }.stateIn(

View file

@ -5,31 +5,37 @@ import androidx.compose.runtime.mutableStateOf
import androidx.compose.ui.text.TextRange import androidx.compose.ui.text.TextRange
import androidx.compose.ui.text.input.TextFieldValue import androidx.compose.ui.text.input.TextFieldValue
import androidx.lifecycle.ViewModel 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.desktop.lwa.ui.screen.characterSheet.detail.dialog.DiminishedStatDialogUio
import com.pixelized.shared.lwa.model.campaign.Campaign 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.Res
import lwacharactersheet.composeapp.generated.resources.character_sheet__diminished__label import lwacharactersheet.composeapp.generated.resources.character_sheet__diminished__label
import org.jetbrains.compose.resources.getString import org.jetbrains.compose.resources.getString
class CharacterDiminishedViewModel( class CharacterDiminishedViewModel(
private val characterSheetRepository: CharacterSheetRepository, private val campaignRepository: CampaignRepository,
private val networkRepository: NetworkRepository,
) : ViewModel() { ) : ViewModel() {
private val _diminishedDialog = mutableStateOf<DiminishedStatDialogUio?>(null) private val _diminishedDialog = mutableStateOf<DiminishedStatDialogUio?>(null)
val diminishedDialog: State<DiminishedStatDialogUio?> get() = _diminishedDialog val diminishedDialog: State<DiminishedStatDialogUio?> get() = _diminishedDialog
suspend fun showDiminishedDialog( suspend fun showDiminishedDialog(
characterInstanceId: Campaign.CharacterInstance.Id?, characterInstanceId: Campaign.CharacterInstance.Id,
) { ) {
if (characterInstanceId == null) return val characterInstance = campaignRepository.characterInstance(
characterInstanceId = characterInstanceId,
val diminished = 0 // TODO repository.characterDiminishedFlow(id = id).value )
val textFieldValue = mutableStateOf( val textFieldValue = mutableStateOf(
TextFieldValue("$diminished", selection = TextRange(index = 0)) TextFieldValue(
text = "${characterInstance.diminished}",
selection = TextRange(index = 0),
)
) )
_diminishedDialog.value = DiminishedStatDialogUio( _diminishedDialog.value = DiminishedStatDialogUio(
id = characterInstanceId, characterInstanceId = characterInstanceId,
label = getString(resource = Res.string.character_sheet__diminished__label), label = getString(resource = Res.string.character_sheet__diminished__label),
value = { textFieldValue.value }, value = { textFieldValue.value },
onValueChange = { value -> onValueChange = { value ->
@ -45,12 +51,16 @@ class CharacterDiminishedViewModel(
_diminishedDialog.value = null _diminishedDialog.value = null
} }
fun changeDiminished(dialog: DiminishedStatDialogUio) { suspend fun changeDiminished(
val value = dialog.value().text.toIntOrNull() ?: 0 dialog: DiminishedStatDialogUio,
// TODO ) {
// repository.setDiminishedForCharacter( val diminished = dialog.value().text.toIntOrNull() ?: 0
// id = dialog.id, networkRepository.share(
// diminished = value, payload = CampaignMessage.UpdateDiminished(
// ) characterSheetId = dialog.characterInstanceId.characterSheetId,
instanceId = dialog.characterInstanceId.instanceId,
diminished = diminished,
)
)
} }
} }

View file

@ -1,17 +1,28 @@
package com.pixelized.desktop.lwa.ui.screen.campaign.player.detail.header 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.clickable
import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.shape.CircleShape import androidx.compose.foundation.shape.CircleShape
import androidx.compose.material.Icon import androidx.compose.material.Icon
import androidx.compose.material.IconButton import androidx.compose.material.IconButton
import androidx.compose.material.MaterialTheme import androidx.compose.material.MaterialTheme
import androidx.compose.material.Text import androidx.compose.material.Text
import androidx.compose.material.minimumInteractiveComponentSize
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.Stable import androidx.compose.runtime.Stable
import androidx.compose.runtime.State import androidx.compose.runtime.State
@ -19,6 +30,7 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip import androidx.compose.ui.draw.clip
import androidx.compose.ui.text.font.FontWeight 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.text.style.TextOverflow
import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.Dp
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( data class CharacterDetailHeaderUio(
val id: Campaign.CharacterInstance.Id, val id: Campaign.CharacterInstance.Id,
val portrait: String?, val portrait: String?,
val diminished: Int,
val name: String, val name: String,
val hp: String, val hp: String,
val maxHp: String, val maxHp: String,
@ -74,15 +87,35 @@ fun CharacterDetailHeader(
overflow = TextOverflow.Ellipsis, overflow = TextOverflow.Ellipsis,
maxLines = 1, maxLines = 1,
) )
IconButton( Box {
onClick = onDiminished, IconButton(
) { onClick = onDiminished,
Icon( ) {
modifier = Modifier.size(size = 24.dp), Icon(
painter = painterResource(Res.drawable.ic_skull_24dp), modifier = Modifier.size(size = 24.dp),
tint = MaterialTheme.colors.primary, painter = painterResource(Res.drawable.ic_skull_24dp),
contentDescription = null, 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( IconButton(
onClick = onDismissRequest, onClick = onDismissRequest,

View file

@ -216,6 +216,7 @@ class CharacterSheetFactory(
value = skillUseCase.computeSkillValue( value = skillUseCase.computeSkillValue(
sheet = characterSheet, sheet = characterSheet,
skill = skill, skill = skill,
diminished = instance.diminished,
alterations = alterations, alterations = alterations,
), ),
tooltips = skill.description?.let { tooltips = skill.description?.let {
@ -240,6 +241,7 @@ class CharacterSheetFactory(
value = skillUseCase.computeSkillValue( value = skillUseCase.computeSkillValue(
sheet = characterSheet, sheet = characterSheet,
skill = skill, skill = skill,
diminished = instance.diminished,
alterations = alterations, alterations = alterations,
), ),
used = skill.used, used = skill.used,
@ -258,6 +260,7 @@ class CharacterSheetFactory(
value = skillUseCase.computeSkillValue( value = skillUseCase.computeSkillValue(
sheet = characterSheet, sheet = characterSheet,
skill = skill, skill = skill,
diminished = instance.diminished,
alterations = alterations, alterations = alterations,
), ),
used = skill.used, used = skill.used,

View file

@ -50,7 +50,7 @@ import org.jetbrains.compose.resources.stringResource
@Stable @Stable
data class DiminishedStatDialogUio( data class DiminishedStatDialogUio(
val id: Campaign.CharacterInstance.Id, val characterInstanceId: Campaign.CharacterInstance.Id,
val label: String, val label: String,
val value: () -> TextFieldValue, val value: () -> TextFieldValue,
val onValueChange: (TextFieldValue) -> Unit, val onValueChange: (TextFieldValue) -> Unit,

View file

@ -103,41 +103,77 @@ class CampaignService(
// Data manipulation through WebSocket. // Data manipulation through WebSocket.
suspend fun updateCharacterCharacteristic( suspend fun updateCharacteristic(
characterInstanceId: Campaign.CharacterInstance.Id, characterInstanceId: Campaign.CharacterInstance.Id,
characteristic: Campaign.CharacterInstance.Characteristic, characteristic: Campaign.CharacterInstance.Characteristic,
value: Int, value: Int,
) { ) {
// fetch all the current campaign character when {
val characters = campaign.characters.toMutableMap() campaign.characters.containsKey(characterInstanceId) -> {
// update the corresponding character using the use case. // fetch all the current campaign character
characters[characterInstanceId] = useCase.updateCharacteristic( val characters = campaign.characters.toMutableMap()
character = campaign.character(id = characterInstanceId), // update the corresponding character using the use case.
characteristic = characteristic, characters[characterInstanceId] = useCase.updateCharacteristic(
value = value, instance = campaign.character(id = characterInstanceId),
) characteristic = characteristic,
// save the campaign to the disk + update the flow. value = value,
store.save( )
campaign = campaign.copy(characters = characters) // 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( suspend fun updateDiminished(
npcInstanceId: Campaign.CharacterInstance.Id, characterInstanceId: Campaign.CharacterInstance.Id,
characteristic: Campaign.CharacterInstance.Characteristic, diminished: Int,
value: Int,
) { ) {
// fetch all the current campaign character when {
val npcs = campaign.npcs.toMutableMap() campaign.characters.containsKey(characterInstanceId) -> {
// update the corresponding character using the use case. // fetch all the current campaign character
npcs[npcInstanceId] = useCase.updateCharacteristic( val characters = campaign.characters.toMutableMap()
character = campaign.npc(id = npcInstanceId), // update the corresponding character using the use case.
characteristic = characteristic, characters[characterInstanceId] = useCase.updateDiminished(
value = value, instance = campaign.character(id = characterInstanceId),
) diminished = diminished,
// save the campaign to the disk + update the flow. )
store.save( // save the campaign to the disk + update the flow.
campaign = campaign.copy(npcs = npcs) 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)
)
}
}
} }
} }

View file

@ -10,7 +10,6 @@ import kotlinx.coroutines.Job
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json import kotlinx.serialization.json.Json
import java.io.File import java.io.File
@ -18,7 +17,6 @@ class CampaignStore(
private val factory: CampaignJsonFactory, private val factory: CampaignJsonFactory,
private val json: Json, private val json: Json,
) { ) {
private val flow = MutableStateFlow(value = Campaign.EMPTY) private val flow = MutableStateFlow(value = Campaign.EMPTY)
init { init {

View file

@ -3,11 +3,12 @@ package com.pixelized.server.lwa.server
import com.pixelized.server.lwa.model.alteration.AlterationService import com.pixelized.server.lwa.model.alteration.AlterationService
import com.pixelized.server.lwa.model.campaign.CampaignService import com.pixelized.server.lwa.model.campaign.CampaignService
import com.pixelized.server.lwa.model.character.CharacterSheetService 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.model.campaign.CampaignJsonFactory
import com.pixelized.shared.lwa.protocol.websocket.Message 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.RestSynchronisation
import com.pixelized.shared.lwa.protocol.websocket.payload.RollMessage 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 com.pixelized.shared.lwa.protocol.websocket.payload.UpdateSkillUsageMessage
import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.MutableSharedFlow
@ -24,12 +25,23 @@ class Engine(
is RollMessage -> Unit // Nothing to do here. is RollMessage -> Unit // Nothing to do here.
is UpdatePlayerCharacteristicMessage -> { is CampaignMessage -> {
campaignService.updateCharacterCharacteristic( val instanceId = Campaign.CharacterInstance.Id(
characterInstanceId = campaignJsonFactory.convertFromV1(characterInstanceIdJson = data.characterInstanceId), characterSheetId = data.characterSheetId,
characteristic = campaignJsonFactory.convertFromV1(characteristicJson = data.characteristic), instanceId = data.instanceId,
value = data.value,
) )
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( is UpdateSkillUsageMessage -> characterService.updateCharacterSkillUsage(

View file

@ -6,6 +6,7 @@ data class Campaign(
) { ) {
data class CharacterInstance( data class CharacterInstance(
val characteristic: Map<Characteristic, Int>, val characteristic: Map<Characteristic, Int>,
val diminished: Int,
) { ) {
data class Id( data class Id(
val characterSheetId: String, val characterSheetId: String,
@ -16,6 +17,13 @@ data class Campaign(
Damage, Damage,
Power, Power,
} }
companion object {
fun empty() = CharacterInstance(
characteristic = emptyMap(),
diminished = 0,
)
}
} }
companion object { companion object {
@ -27,15 +35,11 @@ data class Campaign(
} }
fun Campaign.character(id: Campaign.CharacterInstance.Id): Campaign.CharacterInstance { fun Campaign.character(id: Campaign.CharacterInstance.Id): Campaign.CharacterInstance {
return characters[id] ?: Campaign.CharacterInstance( return characters[id] ?: Campaign.CharacterInstance.empty()
characteristic = emptyMap(),
)
} }
fun Campaign.npc(id: Campaign.CharacterInstance.Id): Campaign.CharacterInstance { fun Campaign.npc(id: Campaign.CharacterInstance.Id): Campaign.CharacterInstance {
return npcs[id] ?: Campaign.CharacterInstance( return npcs[id] ?: Campaign.CharacterInstance.empty()
characteristic = emptyMap(),
)
} }
val Campaign.CharacterInstance.level val Campaign.CharacterInstance.level

View file

@ -47,6 +47,7 @@ class CampaignJsonFactory {
characteristic = characterInstanceJson.characteristic characteristic = characterInstanceJson.characteristic
.map { char -> convertFromV1(characteristicJson = char.key) to char.value } .map { char -> convertFromV1(characteristicJson = char.key) to char.value }
.toMap(), .toMap(),
diminished = characterInstanceJson.diminished ?: 0,
) )
} }
@ -85,6 +86,7 @@ class CampaignJsonFactory {
characteristic = data.characteristic characteristic = data.characteristic
.map { char -> convertToJson(characteristic = char.key) to char.value } .map { char -> convertToJson(characteristic = char.key) to char.value }
.toMap(), .toMap(),
diminished = data.diminished,
) )
} }

View file

@ -11,6 +11,7 @@ data class CampaignJsonV1(
@Serializable @Serializable
data class CharacterInstanceJson( data class CharacterInstanceJson(
val characteristic: Map<Characteristic, Int>, val characteristic: Map<Characteristic, Int>,
val diminished: Int?,
) { ) {
enum class Characteristic { enum class Characteristic {
Damage, Damage,

View file

@ -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
}

View file

@ -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

View file

@ -5,14 +5,23 @@ import com.pixelized.shared.lwa.model.campaign.Campaign
class CampaignUseCase { class CampaignUseCase {
fun updateCharacteristic( fun updateCharacteristic(
character: Campaign.CharacterInstance, instance: Campaign.CharacterInstance,
characteristic: Campaign.CharacterInstance.Characteristic, characteristic: Campaign.CharacterInstance.Characteristic,
value: Int, value: Int,
): Campaign.CharacterInstance { ): Campaign.CharacterInstance {
return character.copy( return instance.copy(
characteristic = character.characteristic.toMutableMap().also { characteristic = instance.characteristic.toMutableMap().also {
it[characteristic] = value it[characteristic] = value
} }
) )
} }
fun updateDiminished(
instance: Campaign.CharacterInstance,
diminished: Int,
): Campaign.CharacterInstance {
return instance.copy(
diminished = diminished,
)
}
} }

View file

@ -24,6 +24,7 @@ class ExpressionUseCase(
fun computeSkillValue( fun computeSkillValue(
sheet: CharacterSheet, sheet: CharacterSheet,
alterations: Map<String, List<FieldAlteration>>, alterations: Map<String, List<FieldAlteration>>,
diminished: Int,
skill: CharacterSheet.Skill, skill: CharacterSheet.Skill,
): Int { ): Int {
val context = Context( val context = Context(
@ -51,7 +52,7 @@ class ExpressionUseCase(
context.evaluate(it.expression) context.evaluate(it.expression)
} ?: 0 } ?: 0
return max(base + bonus + level + alteration, 0) return max(base + bonus + level + alteration - diminished, 0)
} }
fun computeRoll( fun computeRoll(