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(
chracterInstanceId: Campaign.CharacterInstance.Id,
characterInstanceId: Campaign.CharacterInstance.Id,
): 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.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
}
}
}
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.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<String, List<FieldAlteration>> = 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
},
),
)
}
}

View file

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

View file

@ -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<String, List<FieldAlteration>>,
): 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(

View file

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

View file

@ -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<DiminishedStatDialogUio?>(null)
val diminishedDialog: State<DiminishedStatDialogUio?> 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,
)
)
}
}

View file

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

View file

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

View file

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