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,
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,
payload = CampaignMessage.UpdateCharacteristic(
characterSheetId = characterInstanceId.characterSheetId,
instanceId = characterInstanceId.instanceId,
characteristic = characteristicJson,
value = when (characteristic) {
Characteristic.Damage -> {
alteredSheet.maxHp - value
}
Characteristic.Power -> {
alteredSheet.maxPp - value
}
Characteristic.Damage -> alteredSheet.maxHp - value
Characteristic.Power -> alteredSheet.maxPp - value
},
),
)
}
}
}

View file

@ -181,11 +181,13 @@ fun CampaignScreen(
DiminishedStatDialog(
dialog = dismissedViewModel.diminishedDialog,
onConfirm = { diminished ->
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,6 +87,7 @@ fun CharacterDetailHeader(
overflow = TextOverflow.Ellipsis,
maxLines = 1,
)
Box {
IconButton(
onClick = onDiminished,
) {
@ -84,6 +98,25 @@ fun CharacterDetailHeader(
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,

View file

@ -103,16 +103,18 @@ class CampaignService(
// Data manipulation through WebSocket.
suspend fun updateCharacterCharacteristic(
suspend fun updateCharacteristic(
characterInstanceId: Campaign.CharacterInstance.Id,
characteristic: Campaign.CharacterInstance.Characteristic,
value: Int,
) {
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(
character = campaign.character(id = characterInstanceId),
instance = campaign.character(id = characterInstanceId),
characteristic = characteristic,
value = value,
)
@ -122,16 +124,12 @@ class CampaignService(
)
}
suspend fun updateNpcCharacteristic(
npcInstanceId: Campaign.CharacterInstance.Id,
characteristic: Campaign.CharacterInstance.Characteristic,
value: Int,
) {
campaign.npcs.containsKey(characterInstanceId) -> {
// 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),
npcs[characterInstanceId] = useCase.updateCharacteristic(
instance = campaign.npc(id = characterInstanceId),
characteristic = characteristic,
value = value,
)
@ -140,4 +138,42 @@ class CampaignService(
campaign = campaign.copy(npcs = npcs)
)
}
}
}
suspend fun updateDiminished(
characterInstanceId: Campaign.CharacterInstance.Id,
diminished: Int,
) {
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)
)
}
}
}
}

View file

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

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.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),
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(

View file

@ -6,6 +6,7 @@ data class Campaign(
) {
data class CharacterInstance(
val characteristic: Map<Characteristic, Int>,
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

View file

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

View file

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

View file

@ -24,6 +24,7 @@ class ExpressionUseCase(
fun computeSkillValue(
sheet: CharacterSheet,
alterations: Map<String, List<FieldAlteration>>,
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(