From 495768e5fe849b405166af58db18fb585c2bedc3 Mon Sep 17 00:00:00 2001 From: Thomas Andres Gomez Date: Sat, 22 Feb 2025 21:25:08 +0100 Subject: [PATCH] Add characterSheet and Campaing to the server. --- .../com/pixelized/desktop/lwa/Module.kt | 8 +- .../desktop/lwa/business/ExpressionUseCase.kt | 6 +- .../repository/alteration/AlterationStore.kt | 36 ++-- .../repository/campaign/CampaignRepository.kt | 40 ++++ .../lwa/repository/campaign/CampaignStore.kt | 72 +++++++ .../CharacterSheetRepository.kt | 2 +- .../characterSheet/CharacterSheetStore.kt | 57 ++++-- .../repository/network/NetworkRepository.kt | 33 ++++ .../roll_history/RollHistoryRepository.kt | 10 +- .../campaign/player/detail/CharacterDetail.kt | 4 +- .../player/detail/CharacterDetailViewModel.kt | 33 ++-- .../campaign/player/ribbon/PlayerPortrait.kt | 8 +- .../player/ribbon/PlayerRibbonViewModel.kt | 44 +++-- .../detail/CharacterSheetFactory.kt | 23 ++- .../detail/CharacterSheetPage.kt | 1 + .../detail/CharacterSheetViewModel.kt | 126 ++++++------ .../detail/dialog/CharacterSheetStatDialog.kt | 3 +- .../rememberCharacterSheetPagePreview.kt | 2 +- .../edit/CharacterSheetEditFactory.kt | 185 ++++++------------ .../edit/CharacterSheetEditPage.kt | 39 ---- .../lwa/ui/screen/roll/RollViewModel.kt | 2 +- .../rollhistory/RollHistoryViewModel.kt | 2 +- gradle/libs.versions.toml | 4 +- server/build.gradle.kts | 1 + server/src/main/kotlin/Module.kt | 19 +- .../lwa/model/campaign/CampaignService.kt | 49 +++++ .../lwa/model/campaign/CampaignStore.kt | 72 +++++++ .../character/CharacterSheetRepository.kt | 27 --- .../model/character/CharacterSheetService.kt | 41 ++++ .../model/character/CharacterSheetStore.kt | 16 +- .../com/pixelized/server/lwa/server/Engine.kt | 38 ++++ .../com/pixelized/server/lwa/server/Server.kt | 27 ++- .../kotlin/com/pixelized/shared/lwa/Module.kt | 6 +- .../com/pixelized/shared/lwa/StorePath.kt | 9 + .../shared/lwa/model/campaign/Campaign.kt | 34 ++++ .../campaign/{model => }/CampaignJson.kt | 2 +- .../lwa/model/campaign/CampaignJsonFactory.kt | 48 +++++ .../lwa/model/campaign/CampaignJsonV1.kt | 20 ++ .../lwa/model/campaign/CampaignRepository.kt | 9 - .../lwa/model/campaign/model/Campaign.kt | 11 -- .../model/campaign/model/CampaignFactory.kt | 39 ---- .../model/campaign/model/CampaignJsonV1.kt | 15 -- .../{model => }/CharacterSheet.kt | 20 +- .../{model => }/CharacterSheetJson.kt | 2 +- .../{model => }/CharacterSheetJsonFactory.kt | 47 ++--- .../{model => }/CharacterSheetJsonV1.kt | 17 +- .../shared/lwa/protocol/MessageType.kt | 4 +- .../lwa/protocol/payload/MessagePayload.kt | 3 + .../protocol/{roll => payload}/RollMessage.kt | 4 +- .../UpdatePlayerCharacteristicMessage.kt | 11 ++ .../payload/UpdateSkillUsageMessage.kt | 9 + .../shared/lwa/usecase/CampaignUseCase.kt | 18 ++ .../lwa/usecase/CharacterSheetUseCase.kt | 34 +++- 53 files changed, 879 insertions(+), 513 deletions(-) create mode 100644 composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/repository/campaign/CampaignRepository.kt create mode 100644 composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/repository/campaign/CampaignStore.kt create mode 100644 server/src/main/kotlin/com/pixelized/server/lwa/model/campaign/CampaignService.kt create mode 100644 server/src/main/kotlin/com/pixelized/server/lwa/model/campaign/CampaignStore.kt delete mode 100644 server/src/main/kotlin/com/pixelized/server/lwa/model/character/CharacterSheetRepository.kt create mode 100644 server/src/main/kotlin/com/pixelized/server/lwa/model/character/CharacterSheetService.kt create mode 100644 server/src/main/kotlin/com/pixelized/server/lwa/server/Engine.kt create mode 100644 shared/src/commonMain/kotlin/com/pixelized/shared/lwa/model/campaign/Campaign.kt rename shared/src/commonMain/kotlin/com/pixelized/shared/lwa/model/campaign/{model => }/CampaignJson.kt (61%) create mode 100644 shared/src/commonMain/kotlin/com/pixelized/shared/lwa/model/campaign/CampaignJsonFactory.kt create mode 100644 shared/src/commonMain/kotlin/com/pixelized/shared/lwa/model/campaign/CampaignJsonV1.kt delete mode 100644 shared/src/commonMain/kotlin/com/pixelized/shared/lwa/model/campaign/CampaignRepository.kt delete mode 100644 shared/src/commonMain/kotlin/com/pixelized/shared/lwa/model/campaign/model/Campaign.kt delete mode 100644 shared/src/commonMain/kotlin/com/pixelized/shared/lwa/model/campaign/model/CampaignFactory.kt delete mode 100644 shared/src/commonMain/kotlin/com/pixelized/shared/lwa/model/campaign/model/CampaignJsonV1.kt rename shared/src/commonMain/kotlin/com/pixelized/shared/lwa/model/characterSheet/{model => }/CharacterSheet.kt (83%) rename shared/src/commonMain/kotlin/com/pixelized/shared/lwa/model/characterSheet/{model => }/CharacterSheetJson.kt (60%) rename shared/src/commonMain/kotlin/com/pixelized/shared/lwa/model/characterSheet/{model => }/CharacterSheetJsonFactory.kt (75%) rename shared/src/commonMain/kotlin/com/pixelized/shared/lwa/model/characterSheet/{model => }/CharacterSheetJsonV1.kt (74%) create mode 100644 shared/src/commonMain/kotlin/com/pixelized/shared/lwa/protocol/payload/MessagePayload.kt rename shared/src/commonMain/kotlin/com/pixelized/shared/lwa/protocol/{roll => payload}/RollMessage.kt (79%) create mode 100644 shared/src/commonMain/kotlin/com/pixelized/shared/lwa/protocol/payload/UpdatePlayerCharacteristicMessage.kt create mode 100644 shared/src/commonMain/kotlin/com/pixelized/shared/lwa/protocol/payload/UpdateSkillUsageMessage.kt create mode 100644 shared/src/commonMain/kotlin/com/pixelized/shared/lwa/usecase/CampaignUseCase.kt diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/Module.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/Module.kt index 99240f1..f9eb8ea 100644 --- a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/Module.kt +++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/Module.kt @@ -9,6 +9,8 @@ import com.pixelized.desktop.lwa.parser.expression.ExpressionParser import com.pixelized.desktop.lwa.parser.word.WordParser import com.pixelized.desktop.lwa.repository.alteration.AlterationRepository import com.pixelized.desktop.lwa.repository.alteration.AlterationStore +import com.pixelized.desktop.lwa.repository.campaign.CampaignRepository +import com.pixelized.desktop.lwa.repository.campaign.CampaignStore import com.pixelized.desktop.lwa.repository.characterSheet.CharacterSheetRepository import com.pixelized.desktop.lwa.repository.characterSheet.CharacterSheetStore import com.pixelized.desktop.lwa.repository.network.NetworkRepository @@ -29,8 +31,7 @@ import com.pixelized.desktop.lwa.ui.screen.network.NetworkFactory import com.pixelized.desktop.lwa.ui.screen.network.NetworkViewModel import com.pixelized.desktop.lwa.ui.screen.roll.RollViewModel import com.pixelized.desktop.lwa.ui.screen.rollhistory.RollHistoryViewModel -import com.pixelized.shared.lwa.model.campaign.CampaignRepository -import com.pixelized.shared.lwa.model.campaign.model.CampaignFactory +import com.pixelized.shared.lwa.model.campaign.CampaignJsonFactory import com.pixelized.shared.lwa.usecase.CharacterSheetUseCase import io.ktor.client.HttpClient import io.ktor.client.engine.HttpClientEngine @@ -78,6 +79,7 @@ val storeDependencies singleOf(::CharacterSheetStore) singleOf(::SettingsStore) singleOf(::AlterationStore) + singleOf(::CampaignStore) } val repositoryDependencies @@ -97,7 +99,7 @@ val factoryDependencies factoryOf(::NetworkFactory) factoryOf(::SkillFieldFactory) factoryOf(::SettingsFactory) - factoryOf(::CampaignFactory) + factoryOf(::CampaignJsonFactory) } val viewModelDependencies diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/business/ExpressionUseCase.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/business/ExpressionUseCase.kt index 3c787dc..ca27eb0 100644 --- a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/business/ExpressionUseCase.kt +++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/business/ExpressionUseCase.kt @@ -4,7 +4,7 @@ package com.pixelized.desktop.lwa.business import com.pixelized.desktop.lwa.parser.expression.Expression import com.pixelized.desktop.lwa.parser.expression.ExpressionParser import com.pixelized.desktop.lwa.parser.word.Word -import com.pixelized.shared.lwa.model.characterSheet.model.CharacterSheet +import com.pixelized.shared.lwa.model.characterSheet.CharacterSheet import kotlin.math.max import kotlin.math.min @@ -32,9 +32,7 @@ class ExpressionUseCase( val bonus = context.evaluate( expression = skill.bonus?.let(expressionParser::parse), ) - val level = context.evaluate( - expression = skill.level?.let(expressionParser::parse), - ) + val level = max((skill.level - 1) * 5, 0) return max(base + bonus + level + alterations, 0) } diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/repository/alteration/AlterationStore.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/repository/alteration/AlterationStore.kt index 4327903..81b9d11 100644 --- a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/repository/alteration/AlterationStore.kt +++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/repository/alteration/AlterationStore.kt @@ -4,24 +4,24 @@ import com.pixelized.desktop.lwa.parser.expression.Expression import com.pixelized.desktop.lwa.parser.expression.ExpressionParser import com.pixelized.desktop.lwa.repository.alteration.model.Alteration import com.pixelized.desktop.lwa.repository.alteration.model.AlterationMetadata -import com.pixelized.shared.lwa.model.characterSheet.model.CharacterSheet.CharacteristicId -import com.pixelized.shared.lwa.model.characterSheet.model.CharacterSheet.CharacteristicId.ARMOR -import com.pixelized.shared.lwa.model.characterSheet.model.CharacterSheet.CharacteristicId.DEX -import com.pixelized.shared.lwa.model.characterSheet.model.CharacterSheet.CharacteristicId.HEI -import com.pixelized.shared.lwa.model.characterSheet.model.CharacterSheet.CharacteristicId.MOV -import com.pixelized.shared.lwa.model.characterSheet.model.CharacterSheet.CharacteristicId.STR -import com.pixelized.shared.lwa.model.characterSheet.model.CharacterSheet.CommonSkillId.ACROBATICS_ID -import com.pixelized.shared.lwa.model.characterSheet.model.CharacterSheet.CommonSkillId.AID_ID -import com.pixelized.shared.lwa.model.characterSheet.model.CharacterSheet.CommonSkillId.ATHLETICS_ID -import com.pixelized.shared.lwa.model.characterSheet.model.CharacterSheet.CommonSkillId.BARGAIN_ID -import com.pixelized.shared.lwa.model.characterSheet.model.CharacterSheet.CommonSkillId.COMBAT_ID -import com.pixelized.shared.lwa.model.characterSheet.model.CharacterSheet.CommonSkillId.DISCRETION_ID -import com.pixelized.shared.lwa.model.characterSheet.model.CharacterSheet.CommonSkillId.INTIMIDATION_ID -import com.pixelized.shared.lwa.model.characterSheet.model.CharacterSheet.CommonSkillId.PERCEPTION_ID -import com.pixelized.shared.lwa.model.characterSheet.model.CharacterSheet.CommonSkillId.PERSUASION_ID -import com.pixelized.shared.lwa.model.characterSheet.model.CharacterSheet.CommonSkillId.SLEIGHT_OF_HAND_ID -import com.pixelized.shared.lwa.model.characterSheet.model.CharacterSheet.CommonSkillId.SPIEL_ID -import com.pixelized.shared.lwa.model.characterSheet.model.CharacterSheet.CommonSkillId.THROW_ID +import com.pixelized.shared.lwa.model.characterSheet.CharacterSheet.CharacteristicId +import com.pixelized.shared.lwa.model.characterSheet.CharacterSheet.CharacteristicId.ARMOR +import com.pixelized.shared.lwa.model.characterSheet.CharacterSheet.CharacteristicId.DEX +import com.pixelized.shared.lwa.model.characterSheet.CharacterSheet.CharacteristicId.HEI +import com.pixelized.shared.lwa.model.characterSheet.CharacterSheet.CharacteristicId.MOV +import com.pixelized.shared.lwa.model.characterSheet.CharacterSheet.CharacteristicId.STR +import com.pixelized.shared.lwa.model.characterSheet.CharacterSheet.CommonSkillId.ACROBATICS_ID +import com.pixelized.shared.lwa.model.characterSheet.CharacterSheet.CommonSkillId.AID_ID +import com.pixelized.shared.lwa.model.characterSheet.CharacterSheet.CommonSkillId.ATHLETICS_ID +import com.pixelized.shared.lwa.model.characterSheet.CharacterSheet.CommonSkillId.BARGAIN_ID +import com.pixelized.shared.lwa.model.characterSheet.CharacterSheet.CommonSkillId.COMBAT_ID +import com.pixelized.shared.lwa.model.characterSheet.CharacterSheet.CommonSkillId.DISCRETION_ID +import com.pixelized.shared.lwa.model.characterSheet.CharacterSheet.CommonSkillId.INTIMIDATION_ID +import com.pixelized.shared.lwa.model.characterSheet.CharacterSheet.CommonSkillId.PERCEPTION_ID +import com.pixelized.shared.lwa.model.characterSheet.CharacterSheet.CommonSkillId.PERSUASION_ID +import com.pixelized.shared.lwa.model.characterSheet.CharacterSheet.CommonSkillId.SLEIGHT_OF_HAND_ID +import com.pixelized.shared.lwa.model.characterSheet.CharacterSheet.CommonSkillId.SPIEL_ID +import com.pixelized.shared.lwa.model.characterSheet.CharacterSheet.CommonSkillId.THROW_ID class AlterationStore( private val expressionParser: ExpressionParser, 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 new file mode 100644 index 0000000..e80d6fb --- /dev/null +++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/repository/campaign/CampaignRepository.kt @@ -0,0 +1,40 @@ +package com.pixelized.desktop.lwa.repository.campaign + +import com.pixelized.shared.lwa.model.campaign.Campaign +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.Job +import kotlinx.coroutines.flow.SharingStarted +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.mapNotNull +import kotlinx.coroutines.flow.stateIn + +class CampaignRepository( + store: CampaignStore, +) { + private val scope = CoroutineScope(Dispatchers.IO + Job()) + + private val campaign = store.campaignFlow() + .stateIn( + scope = scope, + started = SharingStarted.Eagerly, + initialValue = Campaign.EMPTY, + ) + + fun campaignFlow(): StateFlow = campaign + + fun characterInstance(id: String): StateFlow { + return campaign + .mapNotNull { + it.characters[id] + } + .stateIn( + scope = scope, + started = SharingStarted.Eagerly, + initialValue = campaign.value.characters[id] ?: Campaign.CharacterInstance( + characteristic = emptyMap(), + usedSkill = emptyList(), + ) + ) + } +} \ 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 new file mode 100644 index 0000000..58f386c --- /dev/null +++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/repository/campaign/CampaignStore.kt @@ -0,0 +1,72 @@ +package com.pixelized.desktop.lwa.repository.campaign + +import com.pixelized.desktop.lwa.repository.network.NetworkRepository +import com.pixelized.shared.lwa.model.campaign.Campaign +import com.pixelized.shared.lwa.model.campaign.CampaignJson +import com.pixelized.shared.lwa.model.campaign.CampaignJsonFactory +import com.pixelized.shared.lwa.protocol.MessageType +import com.pixelized.shared.lwa.protocol.payload.UpdatePlayerCharacteristicMessage +import com.pixelized.shared.lwa.usecase.CampaignUseCase +import io.ktor.client.HttpClient +import io.ktor.client.call.body +import io.ktor.client.request.get +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.Job +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.mapNotNull +import kotlinx.coroutines.launch +import kotlinx.serialization.json.Json + +class CampaignStore( + private val network: NetworkRepository, + private val factory: CampaignJsonFactory, + private val useCase: CampaignUseCase, + private val client: HttpClient, + private val json: Json, +) { + private val flow = MutableStateFlow(value = Campaign.EMPTY) + + init { + val scope = CoroutineScope(Dispatchers.IO + Job()) + scope.launch { + flow.value = load() + } + scope.launch { + network.data + .mapNotNull { it.takeIf { it.type == MessageType.UpdatePlayerCharacteristic } } + .map { json.decodeFromString(it.value) } + .collect { + updateCharacteristic(it) + } + } + } + + fun campaignFlow(): StateFlow = flow + + private suspend fun load(): Campaign { + val request: CampaignJson = client + .get("http://pixelized.freeboxos.fr:16030/campaign") // TODO + .body() + val data = factory.convertFromJson(json = request) + return data + } + + private fun updateCharacteristic( + message: UpdatePlayerCharacteristicMessage, + ) { + val characters = flow.value.characters.toMutableMap() + val character = characters[message.characterId] ?: Campaign.CharacterInstance( + characteristic = emptyMap(), + usedSkill = emptyList(), + ) + characters[message.characterId] = useCase.updateCharacteristic( + character = character, + characteristic = message.characteristic, + value = message.value + ) + flow.value = flow.value.copy(characters = characters) + } +} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/repository/characterSheet/CharacterSheetRepository.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/repository/characterSheet/CharacterSheetRepository.kt index 7aad9e7..2d1cee5 100644 --- a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/repository/characterSheet/CharacterSheetRepository.kt +++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/repository/characterSheet/CharacterSheetRepository.kt @@ -1,6 +1,6 @@ package com.pixelized.desktop.lwa.repository.characterSheet -import com.pixelized.shared.lwa.model.characterSheet.model.CharacterSheet +import com.pixelized.shared.lwa.model.characterSheet.CharacterSheet import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Job diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/repository/characterSheet/CharacterSheetStore.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/repository/characterSheet/CharacterSheetStore.kt index faae76a..6b9aaa7 100644 --- a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/repository/characterSheet/CharacterSheetStore.kt +++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/repository/characterSheet/CharacterSheetStore.kt @@ -1,24 +1,31 @@ package com.pixelized.desktop.lwa.repository.characterSheet -import com.pixelized.shared.lwa.model.characterSheet.model.CharacterSheet -import com.pixelized.shared.lwa.model.characterSheet.model.CharacterSheetJson -import com.pixelized.shared.lwa.model.characterSheet.model.CharacterSheetJsonFactory +import com.pixelized.desktop.lwa.repository.network.NetworkRepository +import com.pixelized.shared.lwa.model.characterSheet.CharacterSheet +import com.pixelized.shared.lwa.model.characterSheet.CharacterSheetJson +import com.pixelized.shared.lwa.model.characterSheet.CharacterSheetJsonFactory +import com.pixelized.shared.lwa.protocol.MessageType +import com.pixelized.shared.lwa.protocol.payload.UpdateSkillUsageMessage +import com.pixelized.shared.lwa.usecase.CharacterSheetUseCase import io.ktor.client.HttpClient import io.ktor.client.call.body -import io.ktor.client.plugins.contentnegotiation.ContentNegotiation import io.ktor.client.request.get -import io.ktor.serialization.kotlinx.json.json import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Job import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.mapNotNull import kotlinx.coroutines.launch import kotlinx.serialization.json.Json class CharacterSheetStore( + private val network: NetworkRepository, private val factory: CharacterSheetJsonFactory, + private val useCase: CharacterSheetUseCase, private val client: HttpClient, + private val json: Json, ) { private val flow = MutableStateFlow>(value = emptyList()) @@ -27,21 +34,24 @@ class CharacterSheetStore( scope.launch { flow.value = load() } + scope.launch { + network.data + .mapNotNull { it.takeIf { it.type == MessageType.UpdateSkillUsage } } + .map { json.decodeFromString(it.value) } + .collect { + updateCharacterSkillChange( + characterId = it.characterId, + skillId = it.skillId, + ) + } + } } fun characterSheetFlow(): StateFlow> = flow - fun save(sheet: CharacterSheet) { - - } - - fun delete(id: String): Boolean { - return false - } - suspend fun load(): List { val request: List = client - .get("http://pixelized.freeboxos.fr:16030/characters") + .get("http://pixelized.freeboxos.fr:16030/characters") // TODO .body() val data = request.map { factory.convertFromJson(json = it) @@ -49,8 +59,19 @@ class CharacterSheetStore( return data } - sealed class CharacterSheetStoreException(root: Exception) : Exception(root) - class JsonConversionException(root: Exception) : CharacterSheetStoreException(root) - class FileWriteException(root: Exception) : CharacterSheetStoreException(root) - class FileReadException(root: Exception) : CharacterSheetStoreException(root) + private fun updateCharacterSkillChange( + characterId: String, + skillId: String, + ) { + val characters = flow.value.toMutableList() + val index = characters.indexOfFirst { it.id == characterId } + + if (index > -1) { + characters[index] = useCase.updateSkillUsage( + character = characters[index], + skillId = skillId, + ) + flow.value = characters + } + } } \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/repository/network/NetworkRepository.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/repository/network/NetworkRepository.kt index 35b4abe..71061ad 100644 --- a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/repository/network/NetworkRepository.kt +++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/repository/network/NetworkRepository.kt @@ -7,6 +7,10 @@ import com.pixelized.desktop.lwa.utils.extention.encodeToFrame import com.pixelized.shared.lwa.SERVER_PORT import com.pixelized.shared.lwa.protocol.Message import com.pixelized.shared.lwa.protocol.MessageType +import com.pixelized.shared.lwa.protocol.payload.MessagePayload +import com.pixelized.shared.lwa.protocol.payload.RollMessage +import com.pixelized.shared.lwa.protocol.payload.UpdatePlayerCharacteristicMessage +import com.pixelized.shared.lwa.protocol.payload.UpdateSkillUsageMessage import io.ktor.client.HttpClient import io.ktor.websocket.Frame import kotlinx.coroutines.CoroutineScope @@ -19,11 +23,13 @@ import kotlinx.coroutines.flow.SharedFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.launch import kotlinx.coroutines.runBlocking +import kotlinx.serialization.encodeToString import kotlinx.serialization.json.Json class NetworkRepository( private val settingsRepository: SettingsRepository, private val client: HttpClient, + private val json: Json, ) { companion object { const val DEFAULT_PORT = SERVER_PORT @@ -88,6 +94,33 @@ class NetworkRepository( } } + suspend fun share( + playerName: String = settingsRepository.settings().playerName, + payload: MessagePayload, + ) { + if (status.value == Status.CONNECTED) { + when (payload) { + is RollMessage -> share( + playerName = playerName, + type = MessageType.Roll, + content = json.encodeToString(payload), + ) + + is UpdateSkillUsageMessage -> share( + playerName = playerName, + type = MessageType.UpdateSkillUsage, + content = json.encodeToString(payload), + ) + + is UpdatePlayerCharacteristicMessage -> share( + playerName = playerName, + type = MessageType.UpdatePlayerCharacteristic, + content = json.encodeToString(payload), + ) + } + } + } + suspend fun share( playerName: String = settingsRepository.settings().playerName, type: MessageType, diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/repository/roll_history/RollHistoryRepository.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/repository/roll_history/RollHistoryRepository.kt index c9dc47c..7b9af9a 100644 --- a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/repository/roll_history/RollHistoryRepository.kt +++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/repository/roll_history/RollHistoryRepository.kt @@ -2,7 +2,7 @@ package com.pixelized.desktop.lwa.repository.roll_history import com.pixelized.desktop.lwa.repository.network.NetworkRepository import com.pixelized.shared.lwa.protocol.MessageType -import com.pixelized.shared.lwa.protocol.roll.RollMessage +import com.pixelized.shared.lwa.protocol.payload.RollMessage import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.SharedFlow @@ -28,14 +28,6 @@ class RollHistoryRepository( started = SharingStarted.Eagerly, ) - init { - scope.launch { - network.data.collect { - println(it) - } - } - } - suspend fun share( characterId: String, skillLabel: String, diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/campaign/player/detail/CharacterDetail.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/campaign/player/detail/CharacterDetail.kt index 212f79c..7c54f05 100644 --- a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/campaign/player/detail/CharacterDetail.kt +++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/campaign/player/detail/CharacterDetail.kt @@ -51,13 +51,13 @@ data class CharacterDetailUio( val name: String, val hp: String, val pp: String, + val mov: String, ) @Stable data class CharacterDynDetailUio( val hp: String, val pp: String, - val mov: String, ) @Composable @@ -265,7 +265,7 @@ private fun CharacterHeader( Text( modifier = Modifier.alignByBaseline(), style = MaterialTheme.typography.h6, - text = dynDetail.value.mov, + text = character.mov, ) Text( modifier = Modifier.alignByBaseline(), 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 056dc6d..1d74e55 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 @@ -8,7 +8,10 @@ import androidx.compose.runtime.remember import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import com.pixelized.desktop.lwa.repository.alteration.AlterationRepository +import com.pixelized.desktop.lwa.repository.campaign.CampaignRepository import com.pixelized.desktop.lwa.repository.characterSheet.CharacterSheetRepository +import com.pixelized.shared.lwa.model.campaign.Campaign.CharacterInstance.Characteristic.Damage +import com.pixelized.shared.lwa.model.campaign.Campaign.CharacterInstance.Characteristic.Power import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow @@ -17,24 +20,27 @@ import kotlinx.coroutines.flow.mapNotNull import kotlinx.coroutines.flow.stateIn class CharacterDetailViewModel( - private val repository: CharacterSheetRepository, - private val alteration: AlterationRepository, + private val characterRepository: CharacterSheetRepository, + private val campaignRepository: CampaignRepository, + private val alterationRepository: AlterationRepository, ) : ViewModel() { private val displayedCharacterId = MutableStateFlow(null) val detail: StateFlow = combine( displayedCharacterId, - repository.characterSheetFlow(), - ) { id, sheets -> + characterRepository.characterSheetFlow(), + campaignRepository.campaignFlow(), + ) { id, sheets, campaign -> val sheet = sheets.firstOrNull { it.id == id } if (sheet == null) return@combine null CharacterDetailUio( id = sheet.id, portrait = sheet.portrait, name = sheet.name, - hp = "${sheet.maxHp}", - pp = "${sheet.maxPp}", + hp = "${sheet.hp - (campaign.characters[id]?.characteristic?.get(Damage) ?: 0)}", + pp = "${sheet.pp - (campaign.characters[id]?.characteristic?.get(Power) ?: 0)}", + mov = "${sheet.movement}" ) }.stateIn( scope = viewModelScope, @@ -46,22 +52,19 @@ class CharacterDetailViewModel( @Stable fun collectDynamicDetailAsState(id: String): State { val flow = remember(id) { - repository.characterSheetFlow(id = id) + campaignRepository.characterInstance(id = id) } return remember(id) { flow.mapNotNull { sheet -> - if (sheet == null) return@mapNotNull null CharacterDynDetailUio( - hp = sheet.currentHp.toString(), - pp = sheet.currentPp.toString(), - mov = sheet.movement.toString(), + hp = sheet.characteristic[Damage].toString(), + pp = sheet.characteristic[Power].toString(), ) } }.collectAsState( - initial = CharacterDynDetailUio( - hp = flow.value?.maxHp?.toString() ?: "", - pp = flow.value?.maxPp?.toString() ?: "", - mov = flow.value?.movement?.toString() ?: "", + CharacterDynDetailUio( + hp = flow.value.characteristic[Damage].toString(), + pp = flow.value.characteristic[Power].toString(), ) ) } diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/campaign/player/ribbon/PlayerPortrait.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/campaign/player/ribbon/PlayerPortrait.kt index 29ec973..f8741c6 100644 --- a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/campaign/player/ribbon/PlayerPortrait.kt +++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/campaign/player/ribbon/PlayerPortrait.kt @@ -35,9 +35,9 @@ import org.jetbrains.compose.resources.painterResource data class PlayerPortraitUio( val id: String, val portrait: String?, - val hp: Int, + val damage: Int, val maxHp: Int, - val pp: Int, + val usedPp: Int, val maxPp: Int, ) @@ -96,7 +96,7 @@ fun PlayerPortrait( Text( modifier = Modifier.padding(bottom = 2.dp), style = MaterialTheme.typography.caption, - text = "${character.hp}/${character.maxHp}", + text = "${character.maxHp - character.damage}/${character.maxHp}", ) } Row( @@ -111,7 +111,7 @@ fun PlayerPortrait( Text( modifier = Modifier.padding(bottom = 2.dp), style = MaterialTheme.typography.caption, - text = "${character.pp}/${character.maxPp}", + text = "${character.maxPp - character.usedPp}/${character.maxPp}", ) } } diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/campaign/player/ribbon/PlayerRibbonViewModel.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/campaign/player/ribbon/PlayerRibbonViewModel.kt index 20dbb53..f3faa22 100644 --- a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/campaign/player/ribbon/PlayerRibbonViewModel.kt +++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/campaign/player/ribbon/PlayerRibbonViewModel.kt @@ -8,34 +8,42 @@ import androidx.compose.runtime.State import androidx.compose.runtime.mutableStateOf import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope +import com.pixelized.desktop.lwa.repository.campaign.CampaignRepository import com.pixelized.desktop.lwa.repository.characterSheet.CharacterSheetRepository import com.pixelized.desktop.lwa.repository.roll_history.RollHistoryRepository +import com.pixelized.shared.lwa.model.campaign.character +import com.pixelized.shared.lwa.model.campaign.damage +import com.pixelized.shared.lwa.model.campaign.power import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow -import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.stateIn class PlayerRibbonViewModel( private val rollHistoryRepository: RollHistoryRepository, characterRepository: CharacterSheetRepository, + campaignRepository: CampaignRepository, ) : ViewModel() { - val characters: StateFlow> = characterRepository.characterSheetFlow() - .map { sheets -> - sheets.map { sheet -> - PlayerPortraitUio( - id = sheet.id, - portrait = sheet.thumbnail, - hp = sheet.currentHp, - maxHp = sheet.maxHp, - pp = sheet.currentPp, - maxPp = sheet.maxPp, - ) - } - }.stateIn( - scope = viewModelScope, - started = SharingStarted.Eagerly, - initialValue = emptyList() - ) + val characters: StateFlow> = combine( + characterRepository.characterSheetFlow(), + campaignRepository.campaignFlow(), + ) { sheets, campaign -> + sheets.map { sheet -> + val instance = campaign.character(id = sheet.id) + PlayerPortraitUio( + id = sheet.id, + portrait = sheet.thumbnail, + damage = instance.damage, + maxHp = sheet.hp, + usedPp = instance.power, + maxPp = sheet.pp, + ) + } + }.stateIn( + scope = viewModelScope, + started = SharingStarted.Eagerly, + initialValue = emptyList() + ) private val rolls = hashMapOf>() 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 77c07fe..55dc544 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 @@ -1,12 +1,16 @@ package com.pixelized.desktop.lwa.ui.screen.characterSheet.detail import com.pixelized.desktop.lwa.business.ExpressionUseCase -import com.pixelized.desktop.lwa.ui.composable.tooltip.TooltipUio import com.pixelized.desktop.lwa.repository.alteration.model.FieldAlteration -import com.pixelized.shared.lwa.model.characterSheet.model.CharacterSheet -import com.pixelized.shared.lwa.model.characterSheet.model.CharacterSheet.CharacteristicId +import com.pixelized.desktop.lwa.ui.composable.tooltip.TooltipUio import com.pixelized.desktop.lwa.ui.screen.characterSheet.detail.CharacterSheetPageUio.Characteristic import com.pixelized.desktop.lwa.ui.screen.characterSheet.detail.CharacterSheetPageUio.Node +import com.pixelized.shared.lwa.model.campaign.Campaign +import com.pixelized.shared.lwa.model.campaign.character +import com.pixelized.shared.lwa.model.campaign.damage +import com.pixelized.shared.lwa.model.campaign.power +import com.pixelized.shared.lwa.model.characterSheet.CharacterSheet +import com.pixelized.shared.lwa.model.characterSheet.CharacterSheet.CharacteristicId import lwacharactersheet.composeapp.generated.resources.Res import lwacharactersheet.composeapp.generated.resources.character_sheet__characteristics__cha import lwacharactersheet.composeapp.generated.resources.character_sheet__characteristics__con @@ -45,7 +49,7 @@ class CharacterSheetFactory( suspend fun convertToUio( sheet: CharacterSheet?, - diminished: Int, + campaign: Campaign, alterations: Map>, ): CharacterSheetPageUio? { if (sheet == null) return null @@ -56,6 +60,10 @@ class CharacterSheetFactory( } ?: 0 } + val maxHp = sheet.hp + alterations[CharacteristicId.HP].sum() + val maxPp = sheet.pp + alterations[CharacteristicId.PP].sum() + val instance = campaign.character(sheet.id) + return CharacterSheetPageUio( id = sheet.id, name = sheet.name, @@ -145,7 +153,7 @@ class CharacterSheetFactory( Characteristic( id = CharacteristicId.HP, label = getString(Res.string.character_sheet__sub_characteristics__hit_point), - value = "${sheet.currentHp}/${sheet.maxHp + alterations[CharacteristicId.HP].sum()}", + value = "${maxHp - instance.damage}/${maxHp}", tooltips = TooltipUio( title = getString(Res.string.character_sheet__sub_characteristics__hit_point), description = getString(Res.string.tooltip__sub_characteristics__hit_point), @@ -155,7 +163,7 @@ class CharacterSheetFactory( Characteristic( id = CharacteristicId.PP, label = getString(Res.string.character_sheet__sub_characteristics__power_point), - value = "${sheet.currentPp}/${sheet.maxPp + alterations[CharacteristicId.PP].sum()}", + value = "${maxPp - instance.power}/${maxPp}", tooltips = TooltipUio( title = getString(Res.string.character_sheet__sub_characteristics__power_point), description = getString(Res.string.tooltip__sub_characteristics__power_point), @@ -205,6 +213,7 @@ class CharacterSheetFactory( ), commonSkills = sheet.commonSkills.map { skill -> Node( + id = skill.id, label = skill.label, value = skillUseCase.computeSkillValue( sheet = sheet, @@ -222,6 +231,7 @@ class CharacterSheetFactory( }, specialSKills = sheet.specialSkills.map { skill -> Node( + id = skill.id, label = skill.label, tooltips = skill.description?.takeIf { it.isNotBlank() }?.let { description -> TooltipUio( @@ -239,6 +249,7 @@ class CharacterSheetFactory( }, magicsSkills = sheet.magicSkills.map { skill -> Node( + id = skill.id, label = skill.label, tooltips = skill.description?.takeIf { it.isNotBlank() }?.let { description -> TooltipUio( diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/characterSheet/detail/CharacterSheetPage.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/characterSheet/detail/CharacterSheetPage.kt index 7cbcddc..e79f6fa 100644 --- a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/characterSheet/detail/CharacterSheetPage.kt +++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/characterSheet/detail/CharacterSheetPage.kt @@ -110,6 +110,7 @@ data class CharacterSheetPageUio( @Stable data class Node( + val id: String, val label: String, val value: Int, val tooltips: TooltipUio? = null, diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/characterSheet/detail/CharacterSheetViewModel.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/characterSheet/detail/CharacterSheetViewModel.kt index 6534a1b..d0ef784 100644 --- a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/characterSheet/detail/CharacterSheetViewModel.kt +++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/characterSheet/detail/CharacterSheetViewModel.kt @@ -9,27 +9,40 @@ import androidx.compose.ui.text.input.TextFieldValue import androidx.lifecycle.SavedStateHandle import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope -import com.pixelized.desktop.lwa.ui.navigation.screen.destination.CharacterSheetDestination import com.pixelized.desktop.lwa.repository.alteration.AlterationRepository +import com.pixelized.desktop.lwa.repository.campaign.CampaignRepository import com.pixelized.desktop.lwa.repository.characterSheet.CharacterSheetRepository -import com.pixelized.shared.lwa.model.characterSheet.model.CharacterSheet +import com.pixelized.desktop.lwa.repository.network.NetworkRepository +import com.pixelized.desktop.lwa.ui.navigation.screen.destination.CharacterSheetDestination import com.pixelized.desktop.lwa.ui.screen.characterSheet.detail.dialog.CharacterSheetDeleteConfirmationDialogUio import com.pixelized.desktop.lwa.ui.screen.characterSheet.detail.dialog.DiminishedStatDialogUio import com.pixelized.desktop.lwa.ui.screen.characterSheet.detail.dialog.StatChangeDialogUio import com.pixelized.desktop.lwa.utils.extention.collectAsState +import com.pixelized.shared.lwa.model.campaign.Campaign.CharacterInstance.Characteristic +import com.pixelized.shared.lwa.model.campaign.damage +import com.pixelized.shared.lwa.model.campaign.power +import com.pixelized.shared.lwa.model.characterSheet.CharacterSheet +import com.pixelized.shared.lwa.protocol.payload.UpdatePlayerCharacteristicMessage + +import com.pixelized.shared.lwa.protocol.payload.UpdateSkillUsageMessage +import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.stateIn +import kotlinx.coroutines.launch +import kotlinx.serialization.json.Json import lwacharactersheet.composeapp.generated.resources.Res import lwacharactersheet.composeapp.generated.resources.character_sheet__diminished__label import lwacharactersheet.composeapp.generated.resources.character_sheet_edit__sub_characteristics__hit_point import lwacharactersheet.composeapp.generated.resources.character_sheet_edit__sub_characteristics__power_point import org.jetbrains.compose.resources.getString -import kotlin.math.max -import kotlin.math.min private typealias CSDCDialogUio = CharacterSheetDeleteConfirmationDialogUio class CharacterSheetViewModel( - private val repository: CharacterSheetRepository, + private val characterRepository: CharacterSheetRepository, + private val campaignRepository: CampaignRepository, + private val network: NetworkRepository, + private val json: Json, private val alteration: AlterationRepository, private val factory: CharacterSheetFactory, savedStateHandle: SavedStateHandle, @@ -49,7 +62,7 @@ class CharacterSheetViewModel( private val _diminishedDialog = mutableStateOf(null) val diminishedDialog: State get() = _diminishedDialog - private val diminishedValueFlow = repository.characterDiminishedFlow(id = argument.id) + private val diminishedValueFlow = characterRepository.characterDiminishedFlow(id = argument.id) val diminishedValue: State @Composable get() = diminishedValueFlow.collectAsState { it -> @@ -57,53 +70,46 @@ class CharacterSheetViewModel( } private val sheetFlow = combine( - repository.characterSheetFlow(id = argument.id), - repository.characterDiminishedFlow(id = argument.id), + characterRepository.characterSheetFlow(id = argument.id), + campaignRepository.campaignFlow(), alteration.alterations(characterId = argument.id), - transform = { sheet, diminished, alterations -> - factory.convertToUio(sheet = sheet, diminished = diminished, alterations = alterations) + transform = { sheet, campaign, alterations -> + factory.convertToUio( + sheet = sheet, + campaign = campaign, + alterations = alterations + ) }, + ).stateIn( + scope = viewModelScope, + started = SharingStarted.Eagerly, + initialValue = null, ) val sheet: State @Composable - get() = sheetFlow.collectAsState( - initial = null, - context = viewModelScope.coroutineContext, - ) + get() = sheetFlow.collectAsState() fun toggleWolf() { alteration.toggle(argument.id, "65e37d32-3031-4bf8-9369-d2c45d2efac0") } fun deleteCharacter(id: String) { - repository.delete(id = id) + characterRepository.delete(id = id) } fun onUseSkill(skill: CharacterSheetPageUio.Node) { - repository.characterSheetFlow(id = argument.id).value?.let { sheet -> - - val skills = sheet.commonSkills.map { - if (it.label == skill.label) it.copy(used = it.used.not()) else it - } - val occupations = sheet.specialSkills.map { - if (it.label == skill.label) it.copy(used = it.used.not()) else it - } - val magics = sheet.magicSkills.map { - if (it.label == skill.label) it.copy(used = it.used.not()) else it - } - - repository.save( - characterSheet = sheet.copy( - commonSkills = skills, - specialSkills = occupations, - magicSkills = magics, + viewModelScope.launch { + network.share( + payload = UpdateSkillUsageMessage( + characterId = argument.id, + skillId = skill.id, ) ) } } fun showConfirmCharacterDeletionDialog() { - repository.characterSheetFlow(id = argument.id).value?.let { sheet -> + characterRepository.characterSheetFlow(id = argument.id).value?.let { sheet -> _displayDeleteConfirmationDialog.value = CharacterSheetDeleteConfirmationDialogUio( id = sheet.id, name = sheet.name, @@ -116,35 +122,36 @@ class CharacterSheetViewModel( } suspend fun showSubCharacteristicDialog(id: String) { - repository.characterSheetFlow(id = argument.id).value?.let { sheet -> + characterRepository.characterSheetFlow(id = argument.id).value?.let { sheet -> + val instance = campaignRepository.characterInstance(id = argument.id).value _statChangeDialog.value = when (id) { CharacterSheet.CharacteristicId.HP -> { val value = mutableStateOf( - "${sheet.currentHp}".let { + "${sheet.hp - instance.damage}".let { TextFieldValue(text = it, selection = TextRange(it.length)) } ) StatChangeDialogUio( - id = CharacterSheet.CharacteristicId.HP, + id = Characteristic.Damage, label = getString(resource = Res.string.character_sheet_edit__sub_characteristics__hit_point), value = { value.value }, onValueChange = { value.value = it }, - maxValue = "${sheet.maxHp}", + maxValue = "${sheet.hp}", ) } CharacterSheet.CharacteristicId.PP -> { val value = mutableStateOf( - "${sheet.currentPp}".let { + "${sheet.power - instance.power}".let { TextFieldValue(text = it, selection = TextRange(it.length)) } ) StatChangeDialogUio( - id = CharacterSheet.CharacteristicId.PP, + id = Characteristic.Power, label = getString(resource = Res.string.character_sheet_edit__sub_characteristics__power_point), value = { value.value }, onValueChange = { value.value = it }, - maxValue = "${sheet.maxPp}", + maxValue = "${sheet.power}", ) } @@ -158,29 +165,24 @@ class CharacterSheetViewModel( } fun changeSubCharacteristic( - characteristicId: String, + characteristicId: Characteristic, value: Int, ) { - val sheet = repository.characterSheetFlow(id = argument.id).value - val updated = when (characteristicId) { - CharacterSheet.CharacteristicId.HP -> sheet?.copy( - currentHp = max( - 0, - min(sheet.maxHp, value) + viewModelScope.launch { + val sheet = characterRepository.characterSheetFlow(id = argument.id).value + if (sheet != null) { + network.share( + payload = UpdatePlayerCharacteristicMessage( + characterId = argument.id, + characteristic = characteristicId, + value = when (characteristicId) { + Characteristic.Damage -> sheet.hp - value + Characteristic.Power -> sheet.pp - value + else -> sheet.movement - value + }, + ), ) - ) - - CharacterSheet.CharacteristicId.PP -> sheet?.copy( - currentPp = max( - 0, - min(sheet.maxPp, value) - ) - ) - - else -> null - } - updated?.let { - repository.save(it) + } } } @@ -193,7 +195,7 @@ class CharacterSheetViewModel( } suspend fun showDiminishedDialog() { - val diminished = repository.characterDiminishedFlow(id = argument.id).value + val diminished = characterRepository.characterDiminishedFlow(id = argument.id).value val textFieldValue = mutableStateOf(TextFieldValue("$diminished", selection = TextRange(index = 0))) _diminishedDialog.value = DiminishedStatDialogUio( @@ -215,7 +217,7 @@ class CharacterSheetViewModel( fun changeDiminished(dialog: DiminishedStatDialogUio) { val value = dialog.value().text.toIntOrNull() ?: 0 - repository.setDiminishedForCharacter( + characterRepository.setDiminishedForCharacter( id = dialog.id, diminished = value, ) diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/characterSheet/detail/dialog/CharacterSheetStatDialog.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/characterSheet/detail/dialog/CharacterSheetStatDialog.kt index cb4c0eb..fe61236 100644 --- a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/characterSheet/detail/dialog/CharacterSheetStatDialog.kt +++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/characterSheet/detail/dialog/CharacterSheetStatDialog.kt @@ -42,6 +42,7 @@ import androidx.compose.ui.text.input.TextFieldValue import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp import com.pixelized.desktop.lwa.ui.composable.decoratedBox.DecoratedBox +import com.pixelized.shared.lwa.model.campaign.Campaign import lwacharactersheet.composeapp.generated.resources.Res import lwacharactersheet.composeapp.generated.resources.dialog__cancel_action import lwacharactersheet.composeapp.generated.resources.dialog__confirm_action @@ -49,7 +50,7 @@ import org.jetbrains.compose.resources.stringResource @Stable data class StatChangeDialogUio( - val id: String, + val id: Campaign.CharacterInstance.Characteristic, val label: String, val value: () -> TextFieldValue, val onValueChange: (TextFieldValue) -> Unit, diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/characterSheet/detail/preview/rememberCharacterSheetPagePreview.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/characterSheet/detail/preview/rememberCharacterSheetPagePreview.kt index 774e4da..1bac568 100644 --- a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/characterSheet/detail/preview/rememberCharacterSheetPagePreview.kt +++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/characterSheet/detail/preview/rememberCharacterSheetPagePreview.kt @@ -3,7 +3,7 @@ package com.pixelized.desktop.lwa.ui.screen.characterSheet.detail.preview import androidx.compose.runtime.Composable import androidx.compose.runtime.Stable import androidx.compose.runtime.remember -import com.pixelized.shared.lwa.model.characterSheet.model.CharacterSheet.CharacteristicId +import com.pixelized.shared.lwa.model.characterSheet.CharacterSheet.CharacteristicId import com.pixelized.desktop.lwa.ui.screen.characterSheet.detail.CharacterSheetPageUio import com.pixelized.desktop.lwa.ui.screen.characterSheet.detail.CharacterSheetPageUio.Characteristic diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/characterSheet/edit/CharacterSheetEditFactory.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/characterSheet/edit/CharacterSheetEditFactory.kt index ddc3ac3..db69c7c 100644 --- a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/characterSheet/edit/CharacterSheetEditFactory.kt +++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/characterSheet/edit/CharacterSheetEditFactory.kt @@ -8,7 +8,7 @@ import com.pixelized.desktop.lwa.ui.screen.characterSheet.edit.common.occupation import com.pixelized.desktop.lwa.ui.screen.characterSheet.edit.composable.ActionFieldUio import com.pixelized.desktop.lwa.ui.screen.characterSheet.edit.composable.BaseSkillFieldUio import com.pixelized.desktop.lwa.ui.screen.characterSheet.edit.composable.SimpleFieldUio -import com.pixelized.shared.lwa.model.characterSheet.model.CharacterSheet +import com.pixelized.shared.lwa.model.characterSheet.CharacterSheet import com.pixelized.shared.lwa.usecase.CharacterSheetUseCase import lwacharactersheet.composeapp.generated.resources.Res import lwacharactersheet.composeapp.generated.resources.character_sheet_edit__actions__action_label @@ -41,13 +41,6 @@ import lwacharactersheet.composeapp.generated.resources.character_sheet_edit__sk import lwacharactersheet.composeapp.generated.resources.character_sheet_edit__skills__special_title import lwacharactersheet.composeapp.generated.resources.character_sheet_edit__skills__spiel import lwacharactersheet.composeapp.generated.resources.character_sheet_edit__skills__throw -import lwacharactersheet.composeapp.generated.resources.character_sheet_edit__sub_characteristics__armor -import lwacharactersheet.composeapp.generated.resources.character_sheet_edit__sub_characteristics__damage_bonus -import lwacharactersheet.composeapp.generated.resources.character_sheet_edit__sub_characteristics__hp_grow -import lwacharactersheet.composeapp.generated.resources.character_sheet_edit__sub_characteristics__learning -import lwacharactersheet.composeapp.generated.resources.character_sheet_edit__sub_characteristics__max_hit_point -import lwacharactersheet.composeapp.generated.resources.character_sheet_edit__sub_characteristics__max_power_point -import lwacharactersheet.composeapp.generated.resources.character_sheet_edit__sub_characteristics__movement import org.jetbrains.compose.resources.getString import java.util.UUID import kotlin.math.max @@ -60,56 +53,57 @@ class CharacterSheetEditFactory( currentSheet: CharacterSheet?, editedSheet: CharacterSheetEditPageUio, ): CharacterSheet { - val editedMaxHp = editedSheet.maxHp.unpack()?.toIntOrNull() ?: currentSheet?.maxHp ?: 0 - val editedMaxPp = editedSheet.maxPp.unpack()?.toIntOrNull() ?: currentSheet?.maxPp ?: 0 + + val level = currentSheet?.level ?: 1 + val strength = editedSheet.strength.unpack()?.toIntOrNull() + ?: currentSheet?.strength + ?: 0 + val dexterity = editedSheet.dexterity.unpack()?.toIntOrNull() + ?: currentSheet?.dexterity + ?: 0 + val constitution = editedSheet.constitution.unpack()?.toIntOrNull() + ?: currentSheet?.constitution + ?: 0 + val height = editedSheet.height.unpack()?.toIntOrNull() + ?: currentSheet?.height + ?: 0 + val intelligence = editedSheet.intelligence.unpack()?.toIntOrNull() + ?: currentSheet?.intelligence + ?: 0 + val power = editedSheet.power.unpack()?.toIntOrNull() + ?: currentSheet?.power + ?: 0 + val charisma = editedSheet.charisma.unpack()?.toIntOrNull() + ?: currentSheet?.charisma + ?: 0 + return CharacterSheet( id = editedSheet.id, name = editedSheet.name.value.value, portrait = currentSheet?.portrait, thumbnail = currentSheet?.thumbnail, - strength = editedSheet.strength.unpack()?.toIntOrNull() - ?: currentSheet?.strength - ?: 0, - dexterity = editedSheet.dexterity.unpack()?.toIntOrNull() - ?: currentSheet?.dexterity - ?: 0, - constitution = editedSheet.constitution.unpack()?.toIntOrNull() - ?: currentSheet?.constitution - ?: 0, - height = editedSheet.height.unpack()?.toIntOrNull() - ?: currentSheet?.height - ?: 0, - intelligence = editedSheet.intelligence.unpack()?.toIntOrNull() - ?: currentSheet?.intelligence - ?: 0, - power = editedSheet.power.unpack()?.toIntOrNull() - ?: currentSheet?.power - ?: 0, - charisma = editedSheet.charisma.unpack()?.toIntOrNull() - ?: currentSheet?.charisma - ?: 0, - overrideMovement = editedSheet.movement.value.value.value.isNotBlank(), - movement = editedSheet.movement.unpack()?.toIntOrNull() - ?: currentSheet?.movement - ?: 10, - overrideMaxHp = editedSheet.maxHp.value.value.value.isNotBlank(), - maxHp = editedMaxHp, - currentHp = currentSheet?.currentHp?.coerceAtMost(editedMaxHp) ?: editedMaxHp, - overrideMaxPP = editedSheet.maxPp.value.value.value.isNotBlank(), - maxPp = editedMaxPp, - currentPp = currentSheet?.currentPp?.coerceAtMost(editedMaxPp) ?: editedMaxPp, - overrideDamageBonus = editedSheet.damageBonus.value.value.value.isNotBlank(), - damageBonus = editedSheet.damageBonus.unpack() - ?: currentSheet?.damageBonus - ?: "", - overrideArmor = editedSheet.armor.value.value.value.isNotBlank(), - armor = editedSheet.armor.unpack()?.toIntOrNull() - ?: currentSheet?.armor - ?: 0, - overrideLearning = editedSheet.learning.value.value.value.isNotBlank(), - learning = editedSheet.learning.unpack()?.toIntOrNull() ?: 0, - overrideHpGrow = editedSheet.hpGrow.value.value.value.isNotBlank(), - hpGrow = editedSheet.hpGrow.unpack()?.toIntOrNull() ?: 0, + level = level, + strength = strength, + dexterity = dexterity, + constitution = constitution, + height = height, + intelligence = intelligence, + power = power, + charisma = charisma, + hp = characterSheetUseCase.defaultMaxHp( + constitution = constitution, + height = height, + level = level + ), + pp = characterSheetUseCase.defaultMaxPower(power = power), + movement = characterSheetUseCase.defaultMovement(), + damageBonus = characterSheetUseCase.defaultDamageBonus( + strength = strength, + height = height + ), + armor = characterSheetUseCase.defaultArmor(), + learning = characterSheetUseCase.defaultLearning(intelligence = intelligence), + hpGrow = characterSheetUseCase.defaultHpGrow(constitution = constitution), commonSkills = editedSheet.commonSkills.map { editedSkill -> val currentSkill = currentSheet?.commonSkills?.firstOrNull { it.id == editedSkill.id @@ -120,7 +114,7 @@ class CharacterSheetEditFactory( description = currentSkill?.description, base = "${editedSkill.base.value}", bonus = editedSkill.bonus.value.value.takeIf { it.isNotBlank() }, - level = editedSkill.level.value.value.takeIf { it.isNotBlank() }, + level = editedSkill.level.value.value.toIntOrNull() ?: 0, occupation = editedSkill.option.checked.value, used = currentSkill?.used ?: false, ) @@ -135,7 +129,7 @@ class CharacterSheetEditFactory( description = editedSkill.description.value.value, base = editedSkill.base.value.value, bonus = editedSkill.bonus.value.value.takeIf { it.isNotBlank() }, - level = editedSkill.level.value.value.takeIf { it.isNotBlank() }, + level = editedSkill.level.value.value.toIntOrNull() ?: 0, occupation = editedSkill.options.occupation, used = currentSkill?.used ?: false, ) @@ -150,7 +144,7 @@ class CharacterSheetEditFactory( description = editedSkill.description.value.value, base = editedSkill.base.value.value, bonus = editedSkill.bonus.value.value.takeIf { it.isNotBlank() }, - level = editedSkill.level.value.value.takeIf { it.isNotBlank() }, + level = editedSkill.level.value.value.toIntOrNull() ?: 0, occupation = editedSkill.options.occupation, used = currentSkill?.used ?: false, ) @@ -159,6 +153,8 @@ class CharacterSheetEditFactory( CharacterSheet.Roll( id = it.id, label = it.label.value.value, + description = null, // TODO + canBeCritical = false, // TODO roll = it.action.value.value, ) }, @@ -244,77 +240,6 @@ class CharacterSheetEditFactory( intelligence = int, power = pow, charisma = cha, - movement = SimpleFieldUio( - label = getString(Res.string.character_sheet_edit__sub_characteristics__movement), - value = skillFieldFactory.createWrapper( - value = if (sheet?.overrideMovement == true) "${sheet.movement}" else "", - placeholder = derivedStateOf { - "${characterSheetUseCase.defaultMovement()}" - }, - ) - ), - maxHp = SimpleFieldUio( - label = getString(Res.string.character_sheet_edit__sub_characteristics__max_hit_point), - value = skillFieldFactory.createWrapper( - value = if (sheet?.overrideMaxHp == true) "${sheet.maxHp}" else "", - placeholder = derivedStateOf { - "${ - characterSheetUseCase.defaultMaxHp( - constitution = con(), - height = hei() - ) - }" - }, - ) - ), - maxPp = SimpleFieldUio( - label = getString(Res.string.character_sheet_edit__sub_characteristics__max_power_point), - value = skillFieldFactory.createWrapper( - value = if (sheet?.overrideMaxPP == true) "${sheet.maxPp}" else "", - placeholder = derivedStateOf { - "${characterSheetUseCase.defaultMaxPower(power = pow())}" - }, - ) - ), - damageBonus = SimpleFieldUio( - label = getString(Res.string.character_sheet_edit__sub_characteristics__damage_bonus), - value = skillFieldFactory.createWrapper( - value = if (sheet?.overrideDamageBonus == true) sheet.damageBonus else "", - placeholder = derivedStateOf { - characterSheetUseCase.defaultDamageBonus( - strength = str(), - height = hei() - ) - }, - ) - ), - armor = SimpleFieldUio( - label = getString(Res.string.character_sheet_edit__sub_characteristics__armor), - value = skillFieldFactory.createWrapper( - value = if (sheet?.overrideArmor == true) "${sheet.armor}" else "", - placeholder = derivedStateOf { - "${characterSheetUseCase.defaultArmor()}" - }, - ) - ), - learning = SimpleFieldUio( - label = getString(Res.string.character_sheet_edit__sub_characteristics__learning), - value = skillFieldFactory.createWrapper( - value = if (sheet?.overrideLearning == true) "${sheet.learning}" else "", - placeholder = derivedStateOf { - "${characterSheetUseCase.defaultLearning(intelligence = int())}" - }, - ) - ), - hpGrow = SimpleFieldUio( - label = getString(Res.string.character_sheet_edit__sub_characteristics__hp_grow), - value = skillFieldFactory.createWrapper( - value = if (sheet?.overrideHpGrow == true) "${sheet.hpGrow}" else "", - placeholder = derivedStateOf { - "${characterSheetUseCase.defaultHpGrow(constitution = con())}" - }, - ) - ), commonSkills = listOf( createBaseSkill( sheet = sheet, @@ -421,7 +346,7 @@ class CharacterSheetEditFactory( labelValue = skill.label, baseValue = skill.base, bonusValue = skill.bonus ?: "", - levelValue = skill.level ?: "", + levelValue = skill.level.takeIf { it > 0 }?.toString() ?: "", options = run { val current = sheet.specialSkills.firstOrNull { it.id == skill.id } listOf( @@ -439,7 +364,7 @@ class CharacterSheetEditFactory( labelValue = skill.label, baseValue = skill.base, bonusValue = skill.bonus ?: "", - levelValue = skill.level ?: "", + levelValue = skill.level.takeIf { it > 0 }?.toString() ?: "", options = run { val current = sheet.magicSkills.firstOrNull { it.id == skill.id } listOf( @@ -487,7 +412,7 @@ class CharacterSheetEditFactory( ), level = skillFieldFactory.createWrapper( label = mutableStateOf(getString(Res.string.character_sheet_edit__skills__level_label)), - value = skill?.level ?: "", + value = skill?.level?.takeIf { it > 0 }?.toString() ?: "", ), option = skillFieldFactory.occupationOption(skill?.occupation ?: false), ) diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/characterSheet/edit/CharacterSheetEditPage.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/characterSheet/edit/CharacterSheetEditPage.kt index 64a478a..16d5eb1 100644 --- a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/characterSheet/edit/CharacterSheetEditPage.kt +++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/characterSheet/edit/CharacterSheetEditPage.kt @@ -67,13 +67,6 @@ data class CharacterSheetEditPageUio( val intelligence: SimpleFieldUio, val power: SimpleFieldUio, val charisma: SimpleFieldUio, - val movement: SimpleFieldUio, - val maxHp: SimpleFieldUio, - val maxPp: SimpleFieldUio, - val damageBonus: SimpleFieldUio, - val armor: SimpleFieldUio, - val learning: SimpleFieldUio, - val hpGrow: SimpleFieldUio, val commonSkills: List, val specialSkills: List, val magicSkills: List, @@ -89,17 +82,6 @@ data class CharacterSheetEditPageUio( power, charisma, ) - - val subCharacteristics - get() = listOf( - movement, - maxHp, - maxPp, - damageBonus, - armor, - learning, - hpGrow, - ) } @Composable @@ -215,27 +197,6 @@ fun CharacterSheetEdit( } } } - DecoratedBox( - modifier = Modifier.weight(weight = 1f), - ) { - Column( - horizontalAlignment = Alignment.CenterHorizontally, - ) { - Text( - modifier = Modifier.padding(vertical = 8.dp), - overflow = TextOverflow.Ellipsis, - maxLines = 1, - style = MaterialTheme.typography.caption, - text = stringResource(Res.string.character_sheet_edit__sub_characteristics__title), - ) - form.subCharacteristics.forEach { - SimpleField( - modifier = Modifier.fillMaxWidth(), - field = it, - ) - } - } - } } DecoratedBox( diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/roll/RollViewModel.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/roll/RollViewModel.kt index 254cbe1..80a153a 100644 --- a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/roll/RollViewModel.kt +++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/roll/RollViewModel.kt @@ -12,7 +12,7 @@ import com.pixelized.desktop.lwa.repository.characterSheet.CharacterSheetReposit import com.pixelized.desktop.lwa.repository.roll_history.RollHistoryRepository import com.pixelized.desktop.lwa.ui.screen.characterSheet.detail.CharacterSheetPageUio import com.pixelized.desktop.lwa.ui.screen.roll.DifficultyUio.Difficulty -import com.pixelized.shared.lwa.model.characterSheet.model.CharacterSheet +import com.pixelized.shared.lwa.model.characterSheet.CharacterSheet import kotlinx.coroutines.Job import kotlinx.coroutines.coroutineScope import kotlinx.coroutines.delay diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/rollhistory/RollHistoryViewModel.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/rollhistory/RollHistoryViewModel.kt index 7e605f1..4c18538 100644 --- a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/rollhistory/RollHistoryViewModel.kt +++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/rollhistory/RollHistoryViewModel.kt @@ -6,7 +6,7 @@ import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import com.pixelized.desktop.lwa.repository.characterSheet.CharacterSheetRepository import com.pixelized.desktop.lwa.repository.roll_history.RollHistoryRepository -import com.pixelized.shared.lwa.model.characterSheet.model.CharacterSheet +import com.pixelized.shared.lwa.model.characterSheet.CharacterSheet import kotlinx.coroutines.flow.combine import kotlinx.coroutines.launch diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index e0cb737..63f7f30 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -10,6 +10,7 @@ koin = "4.0.0" turtle = "0.5.0" logback = "1.5.11" coil = "3.1.0" +filament-android = "1.17.1" [plugins] composeMultiplatform = { id = "org.jetbrains.compose", version.ref = "compose-multiplatform" } @@ -50,4 +51,5 @@ turtle = { group = "com.lordcodes.turtle", name = "turtle", version.ref = "turtl logback = { module = "ch.qos.logback:logback-classic", version.ref = "logback" } coil-compose = { module = "io.coil-kt.coil3:coil-compose", version.ref = "coil" } -coil-network = { module = "io.coil-kt.coil3:coil-network-okhttp", version.ref = "coil" } \ No newline at end of file +coil-network = { module = "io.coil-kt.coil3:coil-network-okhttp", version.ref = "coil" } +filament-android = { group = "com.google.ar.sceneform", name = "filament-android", version.ref = "filament-android" } \ No newline at end of file diff --git a/server/build.gradle.kts b/server/build.gradle.kts index 0c3d4d3..fe1793b 100644 --- a/server/build.gradle.kts +++ b/server/build.gradle.kts @@ -22,4 +22,5 @@ dependencies { implementation(libs.ktor.server.websockets) implementation(libs.ktor.server.negotiation) implementation(libs.ktor.serialization.json) + implementation(libs.filament.android) } \ No newline at end of file diff --git a/server/src/main/kotlin/Module.kt b/server/src/main/kotlin/Module.kt index ee43783..083f000 100644 --- a/server/src/main/kotlin/Module.kt +++ b/server/src/main/kotlin/Module.kt @@ -1,5 +1,8 @@ -import com.pixelized.server.lwa.model.character.CharacterSheetRepository +import com.pixelized.server.lwa.model.campaign.CampaignService +import com.pixelized.server.lwa.model.campaign.CampaignStore +import com.pixelized.server.lwa.model.character.CharacterSheetService import com.pixelized.server.lwa.model.character.CharacterSheetStore +import com.pixelized.server.lwa.server.Engine import org.koin.core.module.dsl.singleOf import org.koin.dsl.module @@ -9,17 +12,25 @@ val serverModuleDependencies factoryDependencies, useCaseDependencies, storeDependencies, - repositoryDependencies, + serviceDependencies, + engineDependencies, ) +val engineDependencies + get() = module { + singleOf(::Engine) + } + val storeDependencies get() = module { singleOf(::CharacterSheetStore) + singleOf(::CampaignStore) } -val repositoryDependencies +val serviceDependencies get() = module { - singleOf(::CharacterSheetRepository) + singleOf(::CharacterSheetService) + singleOf(::CampaignService) } val factoryDependencies 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 new file mode 100644 index 0000000..d1665b0 --- /dev/null +++ b/server/src/main/kotlin/com/pixelized/server/lwa/model/campaign/CampaignService.kt @@ -0,0 +1,49 @@ +package com.pixelized.server.lwa.model.campaign + +import com.pixelized.shared.lwa.model.campaign.Campaign +import com.pixelized.shared.lwa.model.campaign.CampaignJson +import com.pixelized.shared.lwa.model.campaign.CampaignJsonFactory +import com.pixelized.shared.lwa.protocol.payload.UpdatePlayerCharacteristicMessage +import com.pixelized.shared.lwa.usecase.CampaignUseCase +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.Job +import kotlinx.coroutines.flow.SharingStarted +import kotlinx.coroutines.flow.stateIn + +class CampaignService( + private val store: CampaignStore, + private val factory: CampaignJsonFactory, + private val useCase: CampaignUseCase, +) { + private val scope = CoroutineScope(Dispatchers.IO + Job()) + private val campaign = store.campaignFlow().stateIn( + scope = scope, + started = SharingStarted.Eagerly, + initialValue = Campaign.EMPTY, + ) + + fun campaign(): CampaignJson { + return campaign.value.let(factory::convertToJson) + } + + suspend fun update( + message: UpdatePlayerCharacteristicMessage, + ) { + // fetch all the current campaign character + val characters = campaign.value.characters.toMutableMap() + // update the corresponding character using the usecase + characters[message.characterId] = useCase.updateCharacteristic( + character = characters[message.characterId] ?: Campaign.CharacterInstance( + characteristic = emptyMap(), + usedSkill = emptyList(), + ), + characteristic = message.characteristic, + value = message.value, + ) + // save the campaign to the disk + update the flow. + store.save( + campaign = campaign.value.copy(characters = characters) + ) + } +} \ 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 new file mode 100644 index 0000000..0470adf --- /dev/null +++ b/server/src/main/kotlin/com/pixelized/server/lwa/model/campaign/CampaignStore.kt @@ -0,0 +1,72 @@ +package com.pixelized.server.lwa.model.campaign + +import com.pixelized.shared.lwa.campaignPath +import com.pixelized.shared.lwa.model.campaign.Campaign +import com.pixelized.shared.lwa.model.campaign.CampaignJson +import com.pixelized.shared.lwa.model.campaign.CampaignJsonFactory +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.Job +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.launch +import kotlinx.serialization.encodeToString +import kotlinx.serialization.json.Json +import java.io.File + +class CampaignStore( + private val factory: CampaignJsonFactory, + private val json: Json, +) { + private val directory = File(campaignPath()).also { it.mkdirs() } + private val flow = MutableStateFlow(value = Campaign.EMPTY) + + init { + val scope = CoroutineScope(Dispatchers.IO + Job()) + scope.launch { + flow.value = load() + } + } + + fun campaignFlow(): StateFlow = flow + + suspend fun load(): Campaign { + return try { + val json = file().readText(charset = Charsets.UTF_8) + if (json.isBlank()) Campaign.EMPTY + val campaign = this.json.decodeFromString(json) + factory.convertFromJson(campaign) + } catch (exception: Exception) { + Campaign.EMPTY + } + } + + suspend fun save(campaign: Campaign) { + // convert the data to json format + val json = try { + factory.convertToJson(data = campaign).let(json::encodeToString) + } catch (exception: Exception) { + throw JsonConversionException(root = exception) + } + // write the file + try { + val file = file() + file.writeText( + text = json, + charset = Charsets.UTF_8, + ) + } catch (exception: Exception) { + throw FileWriteException(root = exception) + } + // Update the dataflow. + flow.value = campaign + } + + sealed class CampaignStoreException(root: Exception) : Exception(root) + class JsonConversionException(root: Exception) : CampaignStoreException(root) + class FileWriteException(root: Exception) : CampaignStoreException(root) + + private fun file(): File { + return File("${campaignPath()}campaign.json") + } +} \ No newline at end of file diff --git a/server/src/main/kotlin/com/pixelized/server/lwa/model/character/CharacterSheetRepository.kt b/server/src/main/kotlin/com/pixelized/server/lwa/model/character/CharacterSheetRepository.kt deleted file mode 100644 index 73d65bb..0000000 --- a/server/src/main/kotlin/com/pixelized/server/lwa/model/character/CharacterSheetRepository.kt +++ /dev/null @@ -1,27 +0,0 @@ -package com.pixelized.server.lwa.model.character - -import com.pixelized.shared.lwa.model.characterSheet.model.CharacterSheet -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.Job -import kotlinx.coroutines.flow.SharingStarted -import kotlinx.coroutines.flow.StateFlow -import kotlinx.coroutines.flow.stateIn - -class CharacterSheetRepository( - store: CharacterSheetStore, -) { - private val scope = CoroutineScope(Dispatchers.IO + Job()) - - private val sheets = store.characterSheetFlow() - .stateIn( - scope = scope, - started = SharingStarted.Eagerly, - initialValue = emptyList() - ) - - fun characterSheetFlow(): StateFlow> { - return sheets - } -} - diff --git a/server/src/main/kotlin/com/pixelized/server/lwa/model/character/CharacterSheetService.kt b/server/src/main/kotlin/com/pixelized/server/lwa/model/character/CharacterSheetService.kt new file mode 100644 index 0000000..92b0649 --- /dev/null +++ b/server/src/main/kotlin/com/pixelized/server/lwa/model/character/CharacterSheetService.kt @@ -0,0 +1,41 @@ +package com.pixelized.server.lwa.model.character + +import com.pixelized.shared.lwa.model.characterSheet.CharacterSheetJson +import com.pixelized.shared.lwa.model.characterSheet.CharacterSheetJsonFactory +import com.pixelized.shared.lwa.usecase.CharacterSheetUseCase +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.Job +import kotlinx.coroutines.flow.SharingStarted +import kotlinx.coroutines.flow.stateIn + +class CharacterSheetService( + private val store: CharacterSheetStore, + private val factory: CharacterSheetJsonFactory, + private val useCase: CharacterSheetUseCase, +) { + private val scope = CoroutineScope(Dispatchers.IO + Job()) + private val sheets = store.characterSheetFlow().stateIn( + scope = scope, + started = SharingStarted.Eagerly, + initialValue = emptyList() + ) + + fun character(): List { + return sheets.value.map(factory::convertToJson) + } + + fun characterSkillChange( + characterId: String, + skillId: String, + ) { + val character = sheets.value.firstOrNull { it.id == characterId } + if (character != null) { + val update = useCase.updateSkillUsage( + character = character, + skillId = skillId, + ) + store.save(sheet = update) + } + } +} \ No newline at end of file diff --git a/server/src/main/kotlin/com/pixelized/server/lwa/model/character/CharacterSheetStore.kt b/server/src/main/kotlin/com/pixelized/server/lwa/model/character/CharacterSheetStore.kt index c81d1e8..6a61e6f 100644 --- a/server/src/main/kotlin/com/pixelized/server/lwa/model/character/CharacterSheetStore.kt +++ b/server/src/main/kotlin/com/pixelized/server/lwa/model/character/CharacterSheetStore.kt @@ -1,9 +1,9 @@ package com.pixelized.server.lwa.model.character import com.pixelized.shared.lwa.characterStorePath -import com.pixelized.shared.lwa.model.characterSheet.model.CharacterSheet -import com.pixelized.shared.lwa.model.characterSheet.model.CharacterSheetJson -import com.pixelized.shared.lwa.model.characterSheet.model.CharacterSheetJsonFactory +import com.pixelized.shared.lwa.model.characterSheet.CharacterSheet +import com.pixelized.shared.lwa.model.characterSheet.CharacterSheetJson +import com.pixelized.shared.lwa.model.characterSheet.CharacterSheetJsonFactory import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Job @@ -17,9 +17,9 @@ import java.text.Collator class CharacterSheetStore( private val factory: CharacterSheetJsonFactory, - private val jsonFormatter: Json, + private val json: Json, ) { - private val characterDirectory = File(characterStorePath()).also { it.mkdirs() } + private val directory = File(characterStorePath()).also { it.mkdirs() } private val flow = MutableStateFlow>(value = emptyList()) init { @@ -39,7 +39,7 @@ class CharacterSheetStore( fun save(sheet: CharacterSheet) { // convert the character sheet into json format. val json = try { - factory.convertToJson(sheet = sheet).let(jsonFormatter::encodeToString) + factory.convertToJson(sheet = sheet).let(json::encodeToString) } catch (exception: Exception) { throw JsonConversionException(root = exception) } @@ -85,7 +85,7 @@ class CharacterSheetStore( JsonConversionException::class, ) suspend fun load(): List { - return characterDirectory + return directory .listFiles() ?.mapNotNull { file -> val json = try { @@ -98,7 +98,7 @@ class CharacterSheetStore( return@mapNotNull null } try { - val sheet = jsonFormatter.decodeFromString(json) + val sheet = this.json.decodeFromString(json) factory.convertFromJson(sheet) } catch (exception: Exception) { throw JsonConversionException(root = exception) 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 new file mode 100644 index 0000000..a94e6f4 --- /dev/null +++ b/server/src/main/kotlin/com/pixelized/server/lwa/server/Engine.kt @@ -0,0 +1,38 @@ +package com.pixelized.server.lwa.server + +import com.pixelized.server.lwa.model.campaign.CampaignService +import com.pixelized.server.lwa.model.character.CharacterSheetService +import com.pixelized.shared.lwa.protocol.Message +import com.pixelized.shared.lwa.protocol.MessageType +import com.pixelized.shared.lwa.protocol.payload.UpdatePlayerCharacteristicMessage +import com.pixelized.shared.lwa.protocol.payload.UpdateSkillUsageMessage +import kotlinx.serialization.json.Json + +class Engine( + private val characterService: CharacterSheetService, + private val campaignService: CampaignService, + private val json: Json, +) { + suspend fun handle(message: Message) { + println(message) + + when (message.type) { + MessageType.Roll -> { + Unit // Nothing to do here. + } + + MessageType.UpdateSkillUsage -> { + val data: UpdateSkillUsageMessage = json.decodeFromString(message.value) + characterService.characterSkillChange( + characterId = data.characterId, + skillId = data.skillId + ) + } + + MessageType.UpdatePlayerCharacteristic -> { + val data: UpdatePlayerCharacteristicMessage = json.decodeFromString(message.value) + campaignService.update(data) + } + } + } +} \ No newline at end of file diff --git a/server/src/main/kotlin/com/pixelized/server/lwa/server/Server.kt b/server/src/main/kotlin/com/pixelized/server/lwa/server/Server.kt index aa36092..56c4c18 100644 --- a/server/src/main/kotlin/com/pixelized/server/lwa/server/Server.kt +++ b/server/src/main/kotlin/com/pixelized/server/lwa/server/Server.kt @@ -3,9 +3,9 @@ package com.pixelized.server.lwa.server import com.pixelized.server.lwa.extention.decodeFromFrame import com.pixelized.server.lwa.extention.encodeToFrame -import com.pixelized.server.lwa.model.character.CharacterSheetRepository +import com.pixelized.server.lwa.model.campaign.CampaignService +import com.pixelized.server.lwa.model.character.CharacterSheetService import com.pixelized.shared.lwa.SERVER_PORT -import com.pixelized.shared.lwa.model.characterSheet.model.CharacterSheetJsonFactory import com.pixelized.shared.lwa.protocol.Message import com.pixelized.shared.lwa.sharedModuleDependencies import io.ktor.http.ContentType @@ -65,24 +65,34 @@ class LocalServer { masking = false } - val repository by inject() - val factory by inject() + val engine by inject() + val characterService by inject() + val campaignService by inject() + routing { get( path = "/", body = { call.respondText(contentType = ContentType.Text.Html) { - "characters" + """""" } } ) get( path = "/characters", body = { - val body = repository.characterSheetFlow().value.map(factory::convertToJson) - call.respond(body) + call.respond(characterService.character()) }, ) + get( + path = "/campaign", + body = { + call.respond(campaignService.campaign()) + } + ) webSocket( path = "/ws", handler = { @@ -97,7 +107,8 @@ class LocalServer { incoming.consumeEach { frame -> if (frame is Frame.Text) { val message = Json.decodeFromFrame(frame = frame) - println(message) + // log the message + engine.handle(message) // broadcast to clients the message outgoingMessageBuffer.emit(message) } diff --git a/shared/src/commonMain/kotlin/com/pixelized/shared/lwa/Module.kt b/shared/src/commonMain/kotlin/com/pixelized/shared/lwa/Module.kt index 15f7676..f9060c9 100644 --- a/shared/src/commonMain/kotlin/com/pixelized/shared/lwa/Module.kt +++ b/shared/src/commonMain/kotlin/com/pixelized/shared/lwa/Module.kt @@ -1,6 +1,8 @@ package com.pixelized.shared.lwa -import com.pixelized.shared.lwa.model.characterSheet.model.CharacterSheetJsonFactory +import com.pixelized.shared.lwa.model.campaign.CampaignJsonFactory +import com.pixelized.shared.lwa.model.characterSheet.CharacterSheetJsonFactory +import com.pixelized.shared.lwa.usecase.CampaignUseCase import com.pixelized.shared.lwa.usecase.CharacterSheetUseCase import kotlinx.serialization.json.Json import org.koin.core.module.dsl.factoryOf @@ -26,9 +28,11 @@ val toolsDependencies val factoryDependencies get() = module { factoryOf(::CharacterSheetJsonFactory) + factoryOf(::CampaignJsonFactory) } val useCaseDependencies get() = module { factoryOf(::CharacterSheetUseCase) + factoryOf(::CampaignUseCase) } \ No newline at end of file diff --git a/shared/src/commonMain/kotlin/com/pixelized/shared/lwa/StorePath.kt b/shared/src/commonMain/kotlin/com/pixelized/shared/lwa/StorePath.kt index aec84c2..2619ae3 100644 --- a/shared/src/commonMain/kotlin/com/pixelized/shared/lwa/StorePath.kt +++ b/shared/src/commonMain/kotlin/com/pixelized/shared/lwa/StorePath.kt @@ -34,4 +34,13 @@ fun characterStorePath( OperatingSystem.Windows -> "${storePath(os = os)}characters\\" OperatingSystem.Macintosh -> "${storePath(os = os)}characters/" } +} + +fun campaignPath( + os: OperatingSystem = OperatingSystem.current, +): String { + return when (os) { + OperatingSystem.Windows -> "${storePath(os = os)}campaign\\" + OperatingSystem.Macintosh -> "${storePath(os = os)}campaign/" + } } \ No newline at end of file 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 new file mode 100644 index 0000000..83cd017 --- /dev/null +++ b/shared/src/commonMain/kotlin/com/pixelized/shared/lwa/model/campaign/Campaign.kt @@ -0,0 +1,34 @@ +package com.pixelized.shared.lwa.model.campaign + +data class Campaign( + val characters: Map, +) { + data class CharacterInstance( + val characteristic: Map, + val usedSkill: List, + ) { + enum class Characteristic { + Damage, + Power, + } + } + + companion object { + val EMPTY = Campaign( + characters = emptyMap(), + ) + } +} + +fun Campaign.character(id: String): Campaign.CharacterInstance { + return characters[id] ?: Campaign.CharacterInstance( + characteristic = emptyMap(), + usedSkill = emptyList(), + ) +} + +val Campaign.CharacterInstance.damage + get() = characteristic[Campaign.CharacterInstance.Characteristic.Damage] ?: 0 + +val Campaign.CharacterInstance.power + get() = characteristic[Campaign.CharacterInstance.Characteristic.Power] ?: 0 \ No newline at end of file diff --git a/shared/src/commonMain/kotlin/com/pixelized/shared/lwa/model/campaign/model/CampaignJson.kt b/shared/src/commonMain/kotlin/com/pixelized/shared/lwa/model/campaign/CampaignJson.kt similarity index 61% rename from shared/src/commonMain/kotlin/com/pixelized/shared/lwa/model/campaign/model/CampaignJson.kt rename to shared/src/commonMain/kotlin/com/pixelized/shared/lwa/model/campaign/CampaignJson.kt index 0f13766..a99af46 100644 --- a/shared/src/commonMain/kotlin/com/pixelized/shared/lwa/model/campaign/model/CampaignJson.kt +++ b/shared/src/commonMain/kotlin/com/pixelized/shared/lwa/model/campaign/CampaignJson.kt @@ -1,4 +1,4 @@ -package com.pixelized.shared.lwa.model.campaign.model +package com.pixelized.shared.lwa.model.campaign import kotlinx.serialization.Serializable 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 new file mode 100644 index 0000000..e008c34 --- /dev/null +++ b/shared/src/commonMain/kotlin/com/pixelized/shared/lwa/model/campaign/CampaignJsonFactory.kt @@ -0,0 +1,48 @@ +package com.pixelized.shared.lwa.model.campaign + + +class CampaignJsonFactory { + fun convertFromJson( + json: CampaignJson, + ): Campaign { + return when (json) { + is CampaignJsonV1 -> convertFromV1(json = json) + } + } + + private fun convertFromV1( + json: CampaignJsonV1, + ): Campaign { + return Campaign( + characters = json.characters.map { entry -> + entry.key to Campaign.CharacterInstance( + characteristic = entry.value.characteristic.map { char -> + when (char.key) { + CampaignJsonV1.CharacterInstanceJson.Characteristic.Damage -> Campaign.CharacterInstance.Characteristic.Damage + CampaignJsonV1.CharacterInstanceJson.Characteristic.Power -> Campaign.CharacterInstance.Characteristic.Power + } to char.value + }.toMap(), + usedSkill = entry.value.usedSkill, + ) + }.toMap() + ) + } + + fun convertToJson( + data: Campaign, + ): CampaignJson { + return CampaignJsonV1( + characters = data.characters.map { entry -> + entry.key to CampaignJsonV1.CharacterInstanceJson( + characteristic = entry.value.characteristic.map { char -> + when (char.key) { + Campaign.CharacterInstance.Characteristic.Damage -> CampaignJsonV1.CharacterInstanceJson.Characteristic.Damage + Campaign.CharacterInstance.Characteristic.Power -> CampaignJsonV1.CharacterInstanceJson.Characteristic.Power + } to char.value + }.toMap(), + usedSkill = entry.value.usedSkill, + ) + }.toMap() + ) + } +} \ No newline at end of file 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 new file mode 100644 index 0000000..9e24bc3 --- /dev/null +++ b/shared/src/commonMain/kotlin/com/pixelized/shared/lwa/model/campaign/CampaignJsonV1.kt @@ -0,0 +1,20 @@ +package com.pixelized.shared.lwa.model.campaign + +import kotlinx.serialization.Serializable + +@Serializable +data class CampaignJsonV1( + val characters: Map, +) : CampaignJson { + + @Serializable + data class CharacterInstanceJson( + val characteristic: Map, + val usedSkill: List, + ) { + enum class Characteristic { + Damage, + Power, + } + } +} \ No newline at end of file diff --git a/shared/src/commonMain/kotlin/com/pixelized/shared/lwa/model/campaign/CampaignRepository.kt b/shared/src/commonMain/kotlin/com/pixelized/shared/lwa/model/campaign/CampaignRepository.kt deleted file mode 100644 index e2212c3..0000000 --- a/shared/src/commonMain/kotlin/com/pixelized/shared/lwa/model/campaign/CampaignRepository.kt +++ /dev/null @@ -1,9 +0,0 @@ -package com.pixelized.shared.lwa.model.campaign - -import com.pixelized.shared.lwa.model.campaign.model.CampaignFactory - -class CampaignRepository( - private val factory: CampaignFactory, -) { - -} \ No newline at end of file diff --git a/shared/src/commonMain/kotlin/com/pixelized/shared/lwa/model/campaign/model/Campaign.kt b/shared/src/commonMain/kotlin/com/pixelized/shared/lwa/model/campaign/model/Campaign.kt deleted file mode 100644 index 14f03e2..0000000 --- a/shared/src/commonMain/kotlin/com/pixelized/shared/lwa/model/campaign/model/Campaign.kt +++ /dev/null @@ -1,11 +0,0 @@ -package com.pixelized.shared.lwa.model.campaign.model - -data class Campaign( - val characters: List, -) { - data class CharacterInstance( - val damage: Int, - val usedPower: Int, - val usedMovement: Int, - ) -} diff --git a/shared/src/commonMain/kotlin/com/pixelized/shared/lwa/model/campaign/model/CampaignFactory.kt b/shared/src/commonMain/kotlin/com/pixelized/shared/lwa/model/campaign/model/CampaignFactory.kt deleted file mode 100644 index d4bb8d8..0000000 --- a/shared/src/commonMain/kotlin/com/pixelized/shared/lwa/model/campaign/model/CampaignFactory.kt +++ /dev/null @@ -1,39 +0,0 @@ -package com.pixelized.shared.lwa.model.campaign.model - -class CampaignFactory { - fun convertFromJson( - json: CampaignJson, - ): Campaign { - return when (json) { - is CampaignJsonV1 -> convertFromV1(json = json) - } - } - - private fun convertFromV1( - json: CampaignJsonV1, - ): Campaign { - return Campaign( - characters = json.characters.map { - Campaign.CharacterInstance( - damage = it.damage, - usedPower = it.usedPower, - usedMovement = it.usedMovement, - ) - } - ) - } - - private fun convertToJson( - data: Campaign, - ): CampaignJson { - return CampaignJsonV1( - characters = data.characters.map { - CampaignJsonV1.CharacterInstanceJson( - damage = it.damage, - usedPower = it.usedPower, - usedMovement = it.usedMovement, - ) - } - ) - } -} \ No newline at end of file diff --git a/shared/src/commonMain/kotlin/com/pixelized/shared/lwa/model/campaign/model/CampaignJsonV1.kt b/shared/src/commonMain/kotlin/com/pixelized/shared/lwa/model/campaign/model/CampaignJsonV1.kt deleted file mode 100644 index bb40815..0000000 --- a/shared/src/commonMain/kotlin/com/pixelized/shared/lwa/model/campaign/model/CampaignJsonV1.kt +++ /dev/null @@ -1,15 +0,0 @@ -package com.pixelized.shared.lwa.model.campaign.model - -import kotlinx.serialization.Serializable - -@Serializable -data class CampaignJsonV1( - val characters: List, -) : CampaignJson { - @Serializable - data class CharacterInstanceJson( - val damage: Int, - val usedPower: Int, - val usedMovement: Int, - ) -} \ No newline at end of file diff --git a/shared/src/commonMain/kotlin/com/pixelized/shared/lwa/model/characterSheet/model/CharacterSheet.kt b/shared/src/commonMain/kotlin/com/pixelized/shared/lwa/model/characterSheet/CharacterSheet.kt similarity index 83% rename from shared/src/commonMain/kotlin/com/pixelized/shared/lwa/model/characterSheet/model/CharacterSheet.kt rename to shared/src/commonMain/kotlin/com/pixelized/shared/lwa/model/characterSheet/CharacterSheet.kt index 7aeb164..afde7bd 100644 --- a/shared/src/commonMain/kotlin/com/pixelized/shared/lwa/model/characterSheet/model/CharacterSheet.kt +++ b/shared/src/commonMain/kotlin/com/pixelized/shared/lwa/model/characterSheet/CharacterSheet.kt @@ -1,10 +1,11 @@ -package com.pixelized.shared.lwa.model.characterSheet.model +package com.pixelized.shared.lwa.model.characterSheet data class CharacterSheet( val id: String, val name: String, val portrait: String?, val thumbnail: String?, + val level: Int, // characteristics val strength: Int, val dexterity: Int, @@ -14,21 +15,12 @@ data class CharacterSheet( val power: Int, val charisma: Int, // sub characteristics - val overrideMovement: Boolean, val movement: Int, - val currentHp: Int, - val overrideMaxHp: Boolean, - val maxHp: Int, - val currentPp: Int, - val overrideMaxPP: Boolean, - val maxPp: Int, - val overrideDamageBonus: Boolean, + val hp: Int, + val pp: Int, val damageBonus: String, - val overrideArmor: Boolean, val armor: Int, - val overrideLearning: Boolean, val learning: Int, - val overrideHpGrow: Boolean, val hpGrow: Int, // skills val commonSkills: List, @@ -43,7 +35,7 @@ data class CharacterSheet( val description: String?, val base: String, val bonus: String?, - val level: String?, + val level: Int, val occupation: Boolean, val used: Boolean, ) @@ -51,6 +43,8 @@ data class CharacterSheet( data class Roll( val id: String, val label: String, + val description: String?, + val canBeCritical: Boolean, val roll: String, ) diff --git a/shared/src/commonMain/kotlin/com/pixelized/shared/lwa/model/characterSheet/model/CharacterSheetJson.kt b/shared/src/commonMain/kotlin/com/pixelized/shared/lwa/model/characterSheet/CharacterSheetJson.kt similarity index 60% rename from shared/src/commonMain/kotlin/com/pixelized/shared/lwa/model/characterSheet/model/CharacterSheetJson.kt rename to shared/src/commonMain/kotlin/com/pixelized/shared/lwa/model/characterSheet/CharacterSheetJson.kt index 1cc7899..ee903ad 100644 --- a/shared/src/commonMain/kotlin/com/pixelized/shared/lwa/model/characterSheet/model/CharacterSheetJson.kt +++ b/shared/src/commonMain/kotlin/com/pixelized/shared/lwa/model/characterSheet/CharacterSheetJson.kt @@ -1,4 +1,4 @@ -package com.pixelized.shared.lwa.model.characterSheet.model +package com.pixelized.shared.lwa.model.characterSheet import kotlinx.serialization.Serializable diff --git a/shared/src/commonMain/kotlin/com/pixelized/shared/lwa/model/characterSheet/model/CharacterSheetJsonFactory.kt b/shared/src/commonMain/kotlin/com/pixelized/shared/lwa/model/characterSheet/CharacterSheetJsonFactory.kt similarity index 75% rename from shared/src/commonMain/kotlin/com/pixelized/shared/lwa/model/characterSheet/model/CharacterSheetJsonFactory.kt rename to shared/src/commonMain/kotlin/com/pixelized/shared/lwa/model/characterSheet/CharacterSheetJsonFactory.kt index 2257342..192f6e7 100644 --- a/shared/src/commonMain/kotlin/com/pixelized/shared/lwa/model/characterSheet/model/CharacterSheetJsonFactory.kt +++ b/shared/src/commonMain/kotlin/com/pixelized/shared/lwa/model/characterSheet/CharacterSheetJsonFactory.kt @@ -1,4 +1,4 @@ -package com.pixelized.shared.lwa.model.characterSheet.model +package com.pixelized.shared.lwa.model.characterSheet import com.pixelized.shared.lwa.usecase.CharacterSheetUseCase @@ -22,6 +22,7 @@ class CharacterSheetJsonFactory( name = json.name, portrait = json.portrait, thumbnail = json.thumbnail, + level = json.level, strength = json.strength, dexterity = json.dexterity, constitution = json.constitution, @@ -29,28 +30,26 @@ class CharacterSheetJsonFactory( intelligence = json.intelligence, power = json.power, charisma = json.charisma, - overrideMovement = json.movement != null, - movement = json.movement ?: defaultMovement(), - currentHp = json.currentHp, - overrideMaxHp = json.maxHp != null, - maxHp = json.maxHp ?: defaultMaxHp( + movement = defaultMovement(), + hp = defaultMaxHp( constitution = json.constitution, height = json.height, + level = json.level ), - currentPp = json.currentPP, - overrideMaxPP = json.maxPP != null, - maxPp = json.maxPP ?: defaultMaxPower(power = json.power), - overrideDamageBonus = json.damageBonus != null, - damageBonus = json.damageBonus ?: defaultDamageBonus( + pp = defaultMaxPower( + power = json.power, + ), + damageBonus = defaultDamageBonus( strength = json.strength, height = json.height, ), - overrideArmor = json.armor != null, - armor = json.armor ?: defaultArmor(), - overrideLearning = json.learning != null, - learning = json.learning ?: defaultLearning(intelligence = json.intelligence), - overrideHpGrow = json.hpGrowf != null, - hpGrow = json.hpGrowf ?: defaultHpGrow(constitution = json.constitution), + armor = defaultArmor(), + learning = defaultLearning( + intelligence = json.intelligence, + ), + hpGrow = defaultHpGrow( + constitution = json.constitution, + ), commonSkills = json.skills.map { CharacterSheet.Skill( id = it.id, @@ -91,6 +90,8 @@ class CharacterSheetJsonFactory( CharacterSheet.Roll( id = it.id, label = it.label, + description = it.description, + canBeCritical = it.canBeCritical, roll = it.roll, ) }, @@ -105,6 +106,7 @@ class CharacterSheetJsonFactory( name = sheet.name, thumbnail = sheet.thumbnail, portrait = sheet.portrait, + level = sheet.level, strength = sheet.strength, dexterity = sheet.dexterity, constitution = sheet.constitution, @@ -112,15 +114,6 @@ class CharacterSheetJsonFactory( intelligence = sheet.intelligence, power = sheet.power, charisma = sheet.charisma, - movement = if (sheet.overrideMovement) sheet.movement else null, - currentHp = sheet.currentHp, - maxHp = if (sheet.overrideMaxHp) sheet.maxHp else null, - currentPP = sheet.currentPp, - maxPP = if (sheet.overrideMaxPP) sheet.maxPp else null, - damageBonus = if (sheet.overrideDamageBonus) sheet.damageBonus else null, - armor = if (sheet.overrideArmor) sheet.armor else null, - learning = if (sheet.overrideLearning) sheet.learning else null, - hpGrowf = if (sheet.overrideHpGrow) sheet.hpGrow else null, skills = sheet.commonSkills.map { CharacterSheetJsonV1.Skill( id = it.id, @@ -161,6 +154,8 @@ class CharacterSheetJsonFactory( CharacterSheetJsonV1.Roll( id = it.id, label = it.label, + description = it.description, + canBeCritical = it.canBeCritical, roll = it.roll, ) }, diff --git a/shared/src/commonMain/kotlin/com/pixelized/shared/lwa/model/characterSheet/model/CharacterSheetJsonV1.kt b/shared/src/commonMain/kotlin/com/pixelized/shared/lwa/model/characterSheet/CharacterSheetJsonV1.kt similarity index 74% rename from shared/src/commonMain/kotlin/com/pixelized/shared/lwa/model/characterSheet/model/CharacterSheetJsonV1.kt rename to shared/src/commonMain/kotlin/com/pixelized/shared/lwa/model/characterSheet/CharacterSheetJsonV1.kt index 0cc6b85..0339be9 100644 --- a/shared/src/commonMain/kotlin/com/pixelized/shared/lwa/model/characterSheet/model/CharacterSheetJsonV1.kt +++ b/shared/src/commonMain/kotlin/com/pixelized/shared/lwa/model/characterSheet/CharacterSheetJsonV1.kt @@ -1,4 +1,4 @@ -package com.pixelized.shared.lwa.model.characterSheet.model +package com.pixelized.shared.lwa.model.characterSheet import kotlinx.serialization.Serializable @@ -8,6 +8,7 @@ data class CharacterSheetJsonV1( val name: String, val portrait: String?, val thumbnail: String?, + val level: Int, // characteristics val strength: Int, val dexterity: Int, @@ -16,16 +17,6 @@ data class CharacterSheetJsonV1( val intelligence: Int, val power: Int, val charisma: Int, - // sub characteristics - val movement: Int?, - val currentHp: Int, - val maxHp: Int?, - val currentPP: Int, - val maxPP: Int?, - val damageBonus: String?, - val armor: Int?, - val learning: Int?, - val hpGrowf: Int?, // skills val skills: List, // occupations @@ -43,7 +34,7 @@ data class CharacterSheetJsonV1( val description: String?, val base: String, val bonus: String?, - val level: String?, + val level: Int, val occupation: Boolean, val used: Boolean, ) @@ -52,6 +43,8 @@ data class CharacterSheetJsonV1( data class Roll( val id: String, val label: String, + val description: String?, + val canBeCritical: Boolean, val roll: String, ) } \ No newline at end of file diff --git a/shared/src/commonMain/kotlin/com/pixelized/shared/lwa/protocol/MessageType.kt b/shared/src/commonMain/kotlin/com/pixelized/shared/lwa/protocol/MessageType.kt index 55361ad..96d19d5 100644 --- a/shared/src/commonMain/kotlin/com/pixelized/shared/lwa/protocol/MessageType.kt +++ b/shared/src/commonMain/kotlin/com/pixelized/shared/lwa/protocol/MessageType.kt @@ -1,5 +1,7 @@ package com.pixelized.shared.lwa.protocol enum class MessageType { - Roll + Roll, + UpdateSkillUsage, + UpdatePlayerCharacteristic, } diff --git a/shared/src/commonMain/kotlin/com/pixelized/shared/lwa/protocol/payload/MessagePayload.kt b/shared/src/commonMain/kotlin/com/pixelized/shared/lwa/protocol/payload/MessagePayload.kt new file mode 100644 index 0000000..b0172f3 --- /dev/null +++ b/shared/src/commonMain/kotlin/com/pixelized/shared/lwa/protocol/payload/MessagePayload.kt @@ -0,0 +1,3 @@ +package com.pixelized.shared.lwa.protocol.payload + +sealed interface MessagePayload \ No newline at end of file diff --git a/shared/src/commonMain/kotlin/com/pixelized/shared/lwa/protocol/roll/RollMessage.kt b/shared/src/commonMain/kotlin/com/pixelized/shared/lwa/protocol/payload/RollMessage.kt similarity index 79% rename from shared/src/commonMain/kotlin/com/pixelized/shared/lwa/protocol/roll/RollMessage.kt rename to shared/src/commonMain/kotlin/com/pixelized/shared/lwa/protocol/payload/RollMessage.kt index b1a503f..7da99e8 100644 --- a/shared/src/commonMain/kotlin/com/pixelized/shared/lwa/protocol/roll/RollMessage.kt +++ b/shared/src/commonMain/kotlin/com/pixelized/shared/lwa/protocol/payload/RollMessage.kt @@ -1,4 +1,4 @@ -package com.pixelized.shared.lwa.protocol.roll +package com.pixelized.shared.lwa.protocol.payload import kotlinx.serialization.Serializable @@ -10,4 +10,4 @@ data class RollMessage( val rollDifficulty: String?, val rollValue: Int, val rollSuccessLimit: Int?, -) \ No newline at end of file +) : MessagePayload \ No newline at end of file diff --git a/shared/src/commonMain/kotlin/com/pixelized/shared/lwa/protocol/payload/UpdatePlayerCharacteristicMessage.kt b/shared/src/commonMain/kotlin/com/pixelized/shared/lwa/protocol/payload/UpdatePlayerCharacteristicMessage.kt new file mode 100644 index 0000000..676de17 --- /dev/null +++ b/shared/src/commonMain/kotlin/com/pixelized/shared/lwa/protocol/payload/UpdatePlayerCharacteristicMessage.kt @@ -0,0 +1,11 @@ +package com.pixelized.shared.lwa.protocol.payload + +import com.pixelized.shared.lwa.model.campaign.Campaign +import kotlinx.serialization.Serializable + +@Serializable +data class UpdatePlayerCharacteristicMessage( + val characterId: String, + val characteristic: Campaign.CharacterInstance.Characteristic, + val value: Int, +) : MessagePayload \ No newline at end of file diff --git a/shared/src/commonMain/kotlin/com/pixelized/shared/lwa/protocol/payload/UpdateSkillUsageMessage.kt b/shared/src/commonMain/kotlin/com/pixelized/shared/lwa/protocol/payload/UpdateSkillUsageMessage.kt new file mode 100644 index 0000000..76592b3 --- /dev/null +++ b/shared/src/commonMain/kotlin/com/pixelized/shared/lwa/protocol/payload/UpdateSkillUsageMessage.kt @@ -0,0 +1,9 @@ +package com.pixelized.shared.lwa.protocol.payload + +import kotlinx.serialization.Serializable + +@Serializable +data class UpdateSkillUsageMessage( + val characterId: String, + val skillId: String, +) : 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 new file mode 100644 index 0000000..9f44153 --- /dev/null +++ b/shared/src/commonMain/kotlin/com/pixelized/shared/lwa/usecase/CampaignUseCase.kt @@ -0,0 +1,18 @@ +package com.pixelized.shared.lwa.usecase + +import com.pixelized.shared.lwa.model.campaign.Campaign + +class CampaignUseCase { + + fun updateCharacteristic( + character: Campaign.CharacterInstance, + characteristic: Campaign.CharacterInstance.Characteristic, + value: Int, + ): Campaign.CharacterInstance { + return character.copy( + characteristic = character.characteristic.toMutableMap().also { + it[characteristic] = value + } + ) + } +} \ No newline at end of file diff --git a/shared/src/commonMain/kotlin/com/pixelized/shared/lwa/usecase/CharacterSheetUseCase.kt b/shared/src/commonMain/kotlin/com/pixelized/shared/lwa/usecase/CharacterSheetUseCase.kt index 92cae41..aa855ce 100644 --- a/shared/src/commonMain/kotlin/com/pixelized/shared/lwa/usecase/CharacterSheetUseCase.kt +++ b/shared/src/commonMain/kotlin/com/pixelized/shared/lwa/usecase/CharacterSheetUseCase.kt @@ -1,5 +1,6 @@ package com.pixelized.shared.lwa.usecase +import com.pixelized.shared.lwa.model.characterSheet.CharacterSheet import kotlin.math.ceil import kotlin.math.max @@ -14,8 +15,10 @@ class CharacterSheetUseCase { fun defaultMaxHp( constitution: Int, height: Int, + level: Int, ): Int { - return (ceil((constitution + height) / 2f).toInt()) + val add = max(defaultHpGrow(constitution = constitution) * (level - 1), 0) + return (ceil((constitution + height) / 2f).toInt()) + add } fun defaultMaxPower( @@ -53,4 +56,33 @@ class CharacterSheetUseCase { fun defaultHpGrow(constitution: Int): Int { return (constitution / 3) } + + fun updateSkillUsage( + character: CharacterSheet, + skillId: String, + ): CharacterSheet { + return character.copy( + commonSkills = character.commonSkills.map { skill -> + if (skill.id == skillId) { + skill.copy(used = skill.used.not()) + } else { + skill + } + }, + specialSkills = character.specialSkills.map { skill -> + if (skill.id == skillId) { + skill.copy(used = skill.used.not()) + } else { + skill + } + }, + magicSkills = character.magicSkills.map { skill -> + if (skill.id == skillId) { + skill.copy(used = skill.used.not()) + } else { + skill + } + } + ) + } }