Add GM & NPC (UI) support. Change the Id system.
This commit is contained in:
parent
6b86a6c075
commit
27dba5438e
54 changed files with 816 additions and 426 deletions
|
|
@ -52,7 +52,7 @@ import com.pixelized.desktop.lwa.ui.navigation.window.destination.RollHistoryWin
|
|||
import com.pixelized.desktop.lwa.ui.navigation.window.rememberMaxWindowHeight
|
||||
import com.pixelized.desktop.lwa.ui.overlay.roll.RollHostState
|
||||
import com.pixelized.desktop.lwa.ui.overlay.roll.RollOverlay
|
||||
import com.pixelized.desktop.lwa.ui.screen.campaign.player.ribbon.PlayerRibbon
|
||||
import com.pixelized.desktop.lwa.ui.screen.campaign.player.CharacterPortraitDefault
|
||||
import com.pixelized.desktop.lwa.ui.screen.characterSheet.CharacterSheetMainNavHost
|
||||
import com.pixelized.desktop.lwa.ui.screen.gamemaster.GameMasterScreen
|
||||
import com.pixelized.desktop.lwa.ui.screen.rollhistory.RollHistoryPage
|
||||
|
|
@ -108,7 +108,7 @@ fun ApplicationScope.LwaApplication() {
|
|||
size = DpSize(
|
||||
width = 800.dp,
|
||||
height = min(
|
||||
a = 56.dp + PlayerRibbon.Default.size.height * 6 + 8.dp * 7 + 40.dp,
|
||||
a = 56.dp + CharacterPortraitDefault.size.height * 6 + 8.dp * 7 + 40.dp,
|
||||
b = maxWindowHeight,
|
||||
),
|
||||
),
|
||||
|
|
|
|||
|
|
@ -43,7 +43,7 @@ class DataSyncViewModel(
|
|||
.filter { status -> status == NetworkRepository.Status.CONNECTED }
|
||||
.combine(campaignRepository.campaignFlow) { _, campaign: Campaign -> campaign }
|
||||
.onEach { campaign ->
|
||||
campaign.characters.keys.forEach { id ->
|
||||
(campaign.characters.keys + campaign.npcs.keys).forEach { id ->
|
||||
characterRepository.characterDetail(
|
||||
characterSheetId = id.characterSheetId,
|
||||
forceUpdate = true,
|
||||
|
|
|
|||
|
|
@ -17,13 +17,13 @@ import com.pixelized.desktop.lwa.ui.composable.character.characteristic.Characte
|
|||
import com.pixelized.desktop.lwa.ui.composable.character.characteristic.CharacterSheetCharacteristicDialogFactory
|
||||
import com.pixelized.desktop.lwa.ui.overlay.roll.RollViewModel
|
||||
import com.pixelized.desktop.lwa.ui.screen.campaign.toolbar.CampaignToolbarViewModel
|
||||
import com.pixelized.desktop.lwa.ui.screen.campaign.chat.CampaignChatViewModel
|
||||
import com.pixelized.desktop.lwa.ui.screen.campaign.chat.TextMessageFactory
|
||||
import com.pixelized.desktop.lwa.ui.screen.campaign.text.CampaignChatViewModel
|
||||
import com.pixelized.desktop.lwa.ui.screen.campaign.text.TextMessageFactory
|
||||
import com.pixelized.desktop.lwa.ui.screen.campaign.player.detail.CharacterDetailFactory
|
||||
import com.pixelized.desktop.lwa.ui.screen.campaign.player.detail.CharacterDetailViewModel
|
||||
import com.pixelized.desktop.lwa.ui.screen.campaign.player.detail.CharacterDiminishedViewModel
|
||||
import com.pixelized.desktop.lwa.ui.screen.campaign.player.ribbon.PlayerRibbonFactory
|
||||
import com.pixelized.desktop.lwa.ui.screen.campaign.player.ribbon.PlayerRibbonViewModel
|
||||
import com.pixelized.desktop.lwa.ui.screen.campaign.player.CharacterRibbonFactory
|
||||
import com.pixelized.desktop.lwa.ui.screen.campaign.player.ribbon.player.PlayerRibbonViewModel
|
||||
import com.pixelized.desktop.lwa.ui.screen.characterSheet.detail.CharacterSheetFactory
|
||||
import com.pixelized.desktop.lwa.ui.screen.characterSheet.detail.CharacterSheetViewModel
|
||||
import com.pixelized.desktop.lwa.ui.screen.characterSheet.edit.CharacterSheetEditFactory
|
||||
|
|
@ -33,6 +33,7 @@ import com.pixelized.desktop.lwa.ui.screen.levelup.LevelUpFactory
|
|||
import com.pixelized.desktop.lwa.ui.screen.levelup.LevelUpViewModel
|
||||
import com.pixelized.desktop.lwa.ui.screen.campaign.network.NetworkFactory
|
||||
import com.pixelized.desktop.lwa.ui.screen.campaign.network.NetworkViewModel
|
||||
import com.pixelized.desktop.lwa.ui.screen.campaign.player.ribbon.npc.NpcRibbonViewModel
|
||||
import com.pixelized.desktop.lwa.ui.screen.gamemaster.GameMasterActionUseCase
|
||||
import com.pixelized.desktop.lwa.ui.screen.gamemaster.GameMasterFactory
|
||||
import com.pixelized.desktop.lwa.ui.screen.gamemaster.GameMasterViewModel
|
||||
|
|
@ -109,7 +110,7 @@ val factoryDependencies
|
|||
factoryOf(::SkillFieldFactory)
|
||||
factoryOf(::SettingsFactory)
|
||||
factoryOf(::CampaignJsonFactory)
|
||||
factoryOf(::PlayerRibbonFactory)
|
||||
factoryOf(::CharacterRibbonFactory)
|
||||
factoryOf(::CharacterDetailFactory)
|
||||
factoryOf(::CharacterSheetCharacteristicDialogFactory)
|
||||
factoryOf(::TextMessageFactory)
|
||||
|
|
@ -127,6 +128,7 @@ val viewModelDependencies
|
|||
viewModelOf(::RollHistoryViewModel)
|
||||
viewModelOf(::NetworkViewModel)
|
||||
viewModelOf(::PlayerRibbonViewModel)
|
||||
viewModelOf(::NpcRibbonViewModel)
|
||||
viewModelOf(::CharacterDetailViewModel)
|
||||
viewModelOf(::CharacterDiminishedViewModel)
|
||||
viewModelOf(::CharacterDetailCharacteristicDialogViewModel)
|
||||
|
|
|
|||
|
|
@ -13,23 +13,28 @@ interface LwaClient {
|
|||
|
||||
suspend fun updateCharacter(sheet: CharacterSheetJson)
|
||||
|
||||
suspend fun deleteCharacter(id: String)
|
||||
suspend fun deleteCharacterSheet(id: String)
|
||||
|
||||
suspend fun campaign(): CampaignJson
|
||||
|
||||
suspend fun campaignAddCharacter(characterSheetId: String)
|
||||
|
||||
suspend fun campaignDeleteCharacter(characterSheetId: String, instanceId: Int)
|
||||
suspend fun campaignRemoveCharacter(characterSheetId: String, instanceId: Int)
|
||||
|
||||
suspend fun campaignAddNpc(characterSheetId: String)
|
||||
|
||||
suspend fun campaignDeleteNpc(characterSheetId: String, instanceId: Int)
|
||||
suspend fun campaignRemoveNpc(characterSheetId: String, instanceId: Int)
|
||||
|
||||
suspend fun alterations(): List<AlterationJson>
|
||||
|
||||
suspend fun activeAlterations(characterSheetId: String, instanceId: Int): List<String>
|
||||
suspend fun activeAlterations(
|
||||
prefix: Char,
|
||||
characterSheetId: String,
|
||||
instanceId: Int,
|
||||
): List<String>
|
||||
|
||||
suspend fun toggleActiveAlterations(
|
||||
prefix: Char,
|
||||
characterSheetId: String,
|
||||
instanceId: Int,
|
||||
alterationId: String,
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ package com.pixelized.desktop.lwa.network
|
|||
|
||||
import com.pixelized.desktop.lwa.repository.settings.SettingsRepository
|
||||
import com.pixelized.shared.lwa.model.alteration.AlterationJson
|
||||
import com.pixelized.shared.lwa.model.campaign.Campaign
|
||||
import com.pixelized.shared.lwa.model.campaign.CampaignJson
|
||||
import com.pixelized.shared.lwa.model.characterSheet.CharacterSheetJson
|
||||
import com.pixelized.shared.lwa.protocol.rest.CharacterPreviewJson
|
||||
|
|
@ -25,7 +26,7 @@ class LwaClientImpl(
|
|||
.body()
|
||||
|
||||
override suspend fun character(id: String): CharacterSheetJson = client
|
||||
.get("$root/character/detail?id=$id")
|
||||
.get("$root/character/detail?characterSheetId=$id")
|
||||
.body()
|
||||
|
||||
override suspend fun updateCharacter(sheet: CharacterSheetJson) = client
|
||||
|
|
@ -35,8 +36,8 @@ class LwaClientImpl(
|
|||
}
|
||||
.body<Unit>()
|
||||
|
||||
override suspend fun deleteCharacter(id: String) = client
|
||||
.delete("$root/character/delete?id=$id")
|
||||
override suspend fun deleteCharacterSheet(id: String) = client
|
||||
.delete("$root/character/delete?characterSheetId=$id")
|
||||
.body<Unit>()
|
||||
|
||||
override suspend fun campaign(): CampaignJson = client
|
||||
|
|
@ -49,11 +50,11 @@ class LwaClientImpl(
|
|||
.put("$root/campaign/character/update?characterSheetId=$characterSheetId")
|
||||
.body<Unit>()
|
||||
|
||||
override suspend fun campaignDeleteCharacter(
|
||||
override suspend fun campaignRemoveCharacter(
|
||||
characterSheetId: String,
|
||||
instanceId: Int,
|
||||
) = client
|
||||
.delete("$root/campaign/character/delete?characterSheetId=$characterSheetId&instanceId=$instanceId")
|
||||
.delete("$root/campaign/character/delete?characterSheetId=$characterSheetId&instanceId=$instanceId&prefix=${Campaign.CharacterInstance.Id.PLAYER}")
|
||||
.body<Unit>()
|
||||
|
||||
override suspend fun campaignAddNpc(
|
||||
|
|
@ -62,11 +63,11 @@ class LwaClientImpl(
|
|||
.put("$root/campaign/npc/update?characterSheetId=$characterSheetId")
|
||||
.body<Unit>()
|
||||
|
||||
override suspend fun campaignDeleteNpc(
|
||||
override suspend fun campaignRemoveNpc(
|
||||
characterSheetId: String,
|
||||
instanceId: Int,
|
||||
) = client
|
||||
.delete("$root/campaign/npc/delete?characterSheetId=$characterSheetId&instanceId=$instanceId")
|
||||
.delete("$root/campaign/npc/delete?characterSheetId=$characterSheetId&instanceId=$instanceId&prefix=${Campaign.CharacterInstance.Id.NPC}")
|
||||
.body<Unit>()
|
||||
|
||||
override suspend fun alterations(): List<AlterationJson> = client
|
||||
|
|
@ -74,18 +75,20 @@ class LwaClientImpl(
|
|||
.body()
|
||||
|
||||
override suspend fun activeAlterations(
|
||||
prefix: Char,
|
||||
characterSheetId: String,
|
||||
instanceId: Int,
|
||||
): List<String> = client
|
||||
.get("$root/alterations/active?characterSheetId=$characterSheetId&instanceId=$instanceId")
|
||||
.get("$root/alterations/active?characterSheetId=$characterSheetId&instanceId=$instanceId&prefix=$prefix")
|
||||
.body()
|
||||
|
||||
override suspend fun toggleActiveAlterations(
|
||||
prefix: Char,
|
||||
characterSheetId: String,
|
||||
instanceId: Int,
|
||||
alterationId: String,
|
||||
) = client
|
||||
.put("$root/alterations/active/toggle?characterSheetId=$characterSheetId&instanceId=$instanceId") {
|
||||
.put("$root/alterations/active/toggle?characterSheetId=$characterSheetId&instanceId=$instanceId&prefix=$prefix") {
|
||||
contentType(ContentType.Application.Json)
|
||||
setBody(alterationId)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -63,7 +63,7 @@ class AlterationRepository(
|
|||
) {
|
||||
// alteration was active for the character toggle it off.
|
||||
store.toggleActiveAlteration(
|
||||
characterInstance = characterInstanceId,
|
||||
characterInstanceId = characterInstanceId,
|
||||
alterationId = alterationId,
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -61,6 +61,7 @@ class AlterationStore(
|
|||
characterInstanceId: CharacterInstance.Id,
|
||||
): List<String> {
|
||||
val request = client.activeAlterations(
|
||||
prefix = characterInstanceId.prefix,
|
||||
characterSheetId = characterInstanceId.characterSheetId,
|
||||
instanceId = characterInstanceId.instanceId,
|
||||
)
|
||||
|
|
@ -76,12 +77,13 @@ class AlterationStore(
|
|||
}
|
||||
|
||||
suspend fun toggleActiveAlteration(
|
||||
characterInstance: CharacterInstance.Id,
|
||||
characterInstanceId: CharacterInstance.Id,
|
||||
alterationId: String,
|
||||
) {
|
||||
client.toggleActiveAlterations(
|
||||
characterSheetId = characterInstance.characterSheetId,
|
||||
instanceId = characterInstance.instanceId,
|
||||
prefix = characterInstanceId.prefix,
|
||||
characterSheetId = characterInstanceId.characterSheetId,
|
||||
instanceId = characterInstanceId.instanceId,
|
||||
alterationId = alterationId,
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -21,20 +21,46 @@ class CampaignRepository(
|
|||
store.update()
|
||||
}
|
||||
|
||||
fun characterInstanceFlow(
|
||||
id: Campaign.CharacterInstance.Id,
|
||||
fun instanceFlow(
|
||||
characterInstanceId: Campaign.CharacterInstance.Id,
|
||||
): StateFlow<Campaign.CharacterInstance> {
|
||||
return campaignFlow
|
||||
.mapNotNull {
|
||||
it.characters[id]
|
||||
it.characters[characterInstanceId] ?: it.npcs[characterInstanceId]
|
||||
}
|
||||
.stateIn(
|
||||
scope = scope,
|
||||
started = SharingStarted.Eagerly,
|
||||
initialValue = campaignFlow.value.character(id = id),
|
||||
initialValue = instance(characterInstanceId),
|
||||
)
|
||||
}
|
||||
|
||||
fun instance(
|
||||
characterInstanceId: Campaign.CharacterInstance.Id,
|
||||
): Campaign.CharacterInstance {
|
||||
return campaignFlow.value.let {
|
||||
it.characters[characterInstanceId]
|
||||
?: it.npcs[characterInstanceId]
|
||||
?: Campaign.CharacterInstance.empty()
|
||||
}
|
||||
}
|
||||
|
||||
@Deprecated(message = "Check if deprecated")
|
||||
fun characterInstanceFlow(
|
||||
characterInstanceId: Campaign.CharacterInstance.Id,
|
||||
): StateFlow<Campaign.CharacterInstance> {
|
||||
return campaignFlow
|
||||
.mapNotNull {
|
||||
it.characters[characterInstanceId]
|
||||
}
|
||||
.stateIn(
|
||||
scope = scope,
|
||||
started = SharingStarted.Eagerly,
|
||||
initialValue = campaignFlow.value.character(id = characterInstanceId),
|
||||
)
|
||||
}
|
||||
@Deprecated(message = "Check if deprecated")
|
||||
|
||||
fun characterInstance(
|
||||
characterInstanceId: Campaign.CharacterInstance.Id,
|
||||
): Campaign.CharacterInstance {
|
||||
|
|
|
|||
|
|
@ -59,7 +59,7 @@ class CampaignStore(
|
|||
characterSheetId: String,
|
||||
instanceId: Int,
|
||||
) {
|
||||
client.campaignDeleteCharacter(
|
||||
client.campaignRemoveCharacter(
|
||||
characterSheetId = characterSheetId,
|
||||
instanceId = instanceId,
|
||||
)
|
||||
|
|
@ -77,7 +77,7 @@ class CampaignStore(
|
|||
characterSheetId: String,
|
||||
instanceId: Int,
|
||||
) {
|
||||
client.campaignDeleteNpc(
|
||||
client.campaignRemoveNpc(
|
||||
characterSheetId = characterSheetId,
|
||||
instanceId = instanceId,
|
||||
)
|
||||
|
|
@ -93,6 +93,7 @@ class CampaignStore(
|
|||
|
||||
is CampaignMessage -> {
|
||||
val instanceId = Campaign.CharacterInstance.Id(
|
||||
prefix = payload.prefix,
|
||||
characterSheetId = payload.characterSheetId,
|
||||
instanceId = payload.instanceId,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -86,7 +86,7 @@ class CharacterSheetStore(
|
|||
characterId: String,
|
||||
) {
|
||||
try {
|
||||
client.deleteCharacter(id = characterId)
|
||||
client.deleteCharacterSheet(id = characterId)
|
||||
_detailFlow.delete(characterId = characterId)
|
||||
} catch (exception: Exception) {
|
||||
// TODO
|
||||
|
|
|
|||
|
|
@ -41,7 +41,7 @@ class CharacterDetailCharacteristicDialogViewModel(
|
|||
val sheet: CharacterSheet? = characterSheetRepository.characterDetail(
|
||||
characterSheetId = characterInstanceId.characterSheetId,
|
||||
)
|
||||
val characterInstance: Campaign.CharacterInstance = campaignRepository.characterInstance(
|
||||
val characterInstance: Campaign.CharacterInstance = campaignRepository.instance(
|
||||
characterInstanceId = characterInstanceId,
|
||||
)
|
||||
val alterations: Map<String, List<FieldAlteration>> = alterationRepository.alterations(
|
||||
|
|
@ -80,6 +80,7 @@ class CharacterDetailCharacteristicDialogViewModel(
|
|||
// share the data through the websocket.
|
||||
network.share(
|
||||
payload = CampaignMessage.UpdateCharacteristic(
|
||||
prefix = characterInstanceId.prefix,
|
||||
characterSheetId = characterInstanceId.characterSheetId,
|
||||
instanceId = characterInstanceId.instanceId,
|
||||
characteristic = characteristicJson,
|
||||
|
|
|
|||
|
|
@ -14,12 +14,17 @@ object CharacterSheetDestination {
|
|||
private const val ROUTE = "character.sheet"
|
||||
private const val CHARACTER_SHEET_ID = "sheetId"
|
||||
private const val CHARACTER_INSTANCE_ID = "instanceId"
|
||||
private const val CHARACTER_PREFIX = "prefix"
|
||||
|
||||
fun baseRoute() = "$ROUTE?${CHARACTER_SHEET_ID.ARG}&${CHARACTER_INSTANCE_ID.ARG}"
|
||||
fun baseRoute() = ROUTE +
|
||||
"?${CHARACTER_PREFIX.ARG}" +
|
||||
"&${CHARACTER_INSTANCE_ID.ARG}" +
|
||||
"&${CHARACTER_SHEET_ID.ARG}"
|
||||
|
||||
fun navigationRoute(characterInstanceId: Campaign.CharacterInstance.Id) = ROUTE +
|
||||
"?$CHARACTER_SHEET_ID=${characterInstanceId.characterSheetId}" +
|
||||
"&$CHARACTER_INSTANCE_ID=${characterInstanceId.instanceId}"
|
||||
"?$CHARACTER_PREFIX=${characterInstanceId.prefix}" +
|
||||
"&$CHARACTER_INSTANCE_ID=${characterInstanceId.instanceId}" +
|
||||
"&$CHARACTER_SHEET_ID=${characterInstanceId.characterSheetId}"
|
||||
|
||||
fun arguments() = listOf(
|
||||
navArgument(CHARACTER_SHEET_ID) {
|
||||
|
|
@ -30,6 +35,10 @@ object CharacterSheetDestination {
|
|||
nullable = false
|
||||
type = NavType.IntType
|
||||
},
|
||||
navArgument(CHARACTER_PREFIX) {
|
||||
nullable = false
|
||||
type = NavType.StringType
|
||||
}
|
||||
)
|
||||
|
||||
data class Argument(
|
||||
|
|
@ -37,6 +46,7 @@ object CharacterSheetDestination {
|
|||
) {
|
||||
constructor(savedStateHandle: SavedStateHandle) : this(
|
||||
characterInstanceId = Campaign.CharacterInstance.Id(
|
||||
savedStateHandle.get<String>(CHARACTER_PREFIX)?.getOrNull(0) ?: error("missing character id"),
|
||||
savedStateHandle.get<String>(CHARACTER_SHEET_ID) ?: error("missing character id"),
|
||||
savedStateHandle.get<Int>(CHARACTER_INSTANCE_ID) ?: error("missing character id"),
|
||||
),
|
||||
|
|
|
|||
|
|
@ -12,14 +12,20 @@ import com.pixelized.shared.lwa.model.campaign.Campaign
|
|||
|
||||
object LevelUpDestination {
|
||||
private const val ROUTE = "levelUp"
|
||||
|
||||
private const val CHARACTER_SHEET_ID = "sheetId"
|
||||
private const val CHARACTER_INSTANCE_ID = "instanceId"
|
||||
private const val CHARACTER_PREFIX = "prefix"
|
||||
|
||||
fun baseRoute() = "${ROUTE}?${CHARACTER_SHEET_ID.ARG}&${CHARACTER_INSTANCE_ID.ARG}"
|
||||
fun baseRoute() = ROUTE +
|
||||
"?${CHARACTER_PREFIX.ARG}" +
|
||||
"&${CHARACTER_INSTANCE_ID.ARG}" +
|
||||
"&${CHARACTER_SHEET_ID.ARG}"
|
||||
|
||||
fun navigationRoute(characterInstanceId: Campaign.CharacterInstance.Id) = ROUTE +
|
||||
"?$CHARACTER_SHEET_ID=${characterInstanceId.characterSheetId}" +
|
||||
"&$CHARACTER_INSTANCE_ID=${characterInstanceId.instanceId}"
|
||||
"?$CHARACTER_PREFIX=${characterInstanceId.prefix}" +
|
||||
"&$CHARACTER_INSTANCE_ID=${characterInstanceId.instanceId}" +
|
||||
"&$CHARACTER_SHEET_ID=${characterInstanceId.characterSheetId}"
|
||||
|
||||
fun arguments() = listOf(
|
||||
navArgument(CHARACTER_SHEET_ID) {
|
||||
|
|
@ -30,6 +36,10 @@ object LevelUpDestination {
|
|||
nullable = false
|
||||
type = NavType.IntType
|
||||
},
|
||||
navArgument(CHARACTER_PREFIX) {
|
||||
nullable = false
|
||||
type = NavType.StringType
|
||||
}
|
||||
)
|
||||
|
||||
data class Argument(
|
||||
|
|
@ -37,6 +47,7 @@ object LevelUpDestination {
|
|||
) {
|
||||
constructor(savedStateHandle: SavedStateHandle) : this(
|
||||
characterInstanceId = Campaign.CharacterInstance.Id(
|
||||
savedStateHandle.get<String>(CHARACTER_PREFIX)?.getOrNull(0) ?: error("missing character id"),
|
||||
savedStateHandle.get<String>(CHARACTER_SHEET_ID) ?: error("missing character id"),
|
||||
savedStateHandle.get<Int>(CHARACTER_INSTANCE_ID) ?: error("missing character id"),
|
||||
),
|
||||
|
|
|
|||
|
|
@ -41,8 +41,7 @@ class RollViewModel(
|
|||
private val alteredCharacterSheetFactory: AlteredCharacterSheetFactory,
|
||||
) : ViewModel() {
|
||||
private var alteredCharacterSheet: AlteredCharacterSheet? = null
|
||||
private var rollAction: String? = null
|
||||
private var rollSuccessValue: Int? = null
|
||||
private var rollAction: RollActionUio? = null
|
||||
|
||||
var lastRollResult: RollResult = RollResult.Dismissed
|
||||
private set
|
||||
|
|
@ -67,7 +66,6 @@ class RollViewModel(
|
|||
suspend fun cleanRoll() {
|
||||
alteredCharacterSheet = null
|
||||
rollAction = null
|
||||
rollSuccessValue = null
|
||||
|
||||
lastRollResult = RollResult.Dismissed
|
||||
|
||||
|
|
@ -106,10 +104,9 @@ class RollViewModel(
|
|||
alterations = alterations,
|
||||
)
|
||||
|
||||
this.rollAction = roll.rollAction
|
||||
this.rollSuccessValue = roll.rollSuccessValue
|
||||
this.rollAction = roll
|
||||
|
||||
val rollStep = rollSuccessValue?.let {
|
||||
val rollStep = roll.rollSuccessValue?.let {
|
||||
skillStepUseCase.computeSkillStep(skill = it)
|
||||
}
|
||||
|
||||
|
|
@ -119,7 +116,7 @@ class RollViewModel(
|
|||
label = roll.label,
|
||||
value = rollStep?.success?.last
|
||||
)
|
||||
_rollDifficulty.value = rollSuccessValue?.let {
|
||||
_rollDifficulty.value = roll.rollSuccessValue?.let {
|
||||
DifficultyUio(
|
||||
open = false,
|
||||
difficulty = Difficulty.NORMAL,
|
||||
|
|
@ -148,7 +145,7 @@ class RollViewModel(
|
|||
delay(500)
|
||||
_cancellable.value = false
|
||||
// compute the skill critical success to critical failure ranges.
|
||||
val rollStep = rollSuccessValue?.let {
|
||||
val rollStep = rollAction.rollSuccessValue?.let {
|
||||
skillStepUseCase.computeSkillStep(
|
||||
skill = when (_rollDifficulty.value?.difficulty) {
|
||||
Difficulty.EASY -> it * 2
|
||||
|
|
@ -163,7 +160,7 @@ class RollViewModel(
|
|||
// compute the roll (typically use the expression inside the rollAction)
|
||||
val roll = skillComputation.computeRoll(
|
||||
sheet = alteredCharacterSheet,
|
||||
expression = rollAction,
|
||||
expression = rollAction.rollAction,
|
||||
)
|
||||
|
||||
// check where the roll fall into the rollSteps.
|
||||
|
|
@ -196,7 +193,6 @@ class RollViewModel(
|
|||
|
||||
launch {
|
||||
shareRollResult(
|
||||
alteredCharacterSheet = alteredCharacterSheet,
|
||||
rollTitle = rollTitle,
|
||||
roll = roll,
|
||||
rollStep = rollStep,
|
||||
|
|
@ -219,7 +215,7 @@ class RollViewModel(
|
|||
open = false,
|
||||
difficulty = difficulty,
|
||||
)
|
||||
val rollStep = rollSuccessValue?.let {
|
||||
val rollStep = rollAction?.rollSuccessValue?.let {
|
||||
skillStepUseCase.computeSkillStep(
|
||||
skill = when (_rollDifficulty.value?.difficulty) {
|
||||
Difficulty.EASY -> it * 2
|
||||
|
|
@ -263,15 +259,18 @@ class RollViewModel(
|
|||
}
|
||||
|
||||
private suspend fun shareRollResult(
|
||||
alteredCharacterSheet: AlteredCharacterSheet,
|
||||
rollTitle: RollTitleUio,
|
||||
roll: Int,
|
||||
rollStep: SkillStepUseCase.SkillStep?,
|
||||
success: String?,
|
||||
) {
|
||||
val rollAction = rollAction ?: return
|
||||
|
||||
val payload = RollMessage(
|
||||
id = RollMessage.RollId.create(),
|
||||
characterSheetId = alteredCharacterSheet.id,
|
||||
prefix = rollAction.characterInstanceId.prefix,
|
||||
characterSheetId = rollAction.characterInstanceId.characterSheetId,
|
||||
instanceId = rollAction.characterInstanceId.instanceId,
|
||||
skillLabel = rollTitle.label,
|
||||
rollValue = roll,
|
||||
resultLabel = success,
|
||||
|
|
|
|||
|
|
@ -26,6 +26,7 @@ import androidx.compose.ui.platform.LocalDensity
|
|||
import androidx.compose.ui.unit.Density
|
||||
import androidx.compose.ui.unit.DpSize
|
||||
import androidx.compose.ui.unit.IntSize
|
||||
import androidx.compose.ui.unit.LayoutDirection
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.pixelized.desktop.lwa.ui.composable.blur.BlurContent
|
||||
import com.pixelized.desktop.lwa.ui.composable.blur.rememberBlurContentController
|
||||
|
|
@ -34,16 +35,17 @@ import com.pixelized.desktop.lwa.ui.composable.character.characteristic.Characte
|
|||
import com.pixelized.desktop.lwa.ui.composable.key.KeyHandler
|
||||
import com.pixelized.desktop.lwa.ui.navigation.screen.LocalScreenController
|
||||
import com.pixelized.desktop.lwa.ui.navigation.screen.destination.navigateToLevelScreen
|
||||
import com.pixelized.desktop.lwa.ui.screen.campaign.chat.CampaignChat
|
||||
import com.pixelized.desktop.lwa.ui.screen.campaign.chat.CampaignChatViewModel
|
||||
import com.pixelized.desktop.lwa.ui.screen.campaign.text.CampaignChat
|
||||
import com.pixelized.desktop.lwa.ui.screen.campaign.text.CampaignChatViewModel
|
||||
import com.pixelized.desktop.lwa.ui.screen.campaign.player.ribbon.npc.NpcRibbon
|
||||
import com.pixelized.desktop.lwa.ui.screen.campaign.player.detail.CharacterDetailPanel
|
||||
import com.pixelized.desktop.lwa.ui.screen.campaign.player.detail.CharacterDetailViewModel
|
||||
import com.pixelized.desktop.lwa.ui.screen.campaign.player.detail.CharacterDiminishedViewModel
|
||||
import com.pixelized.desktop.lwa.ui.screen.campaign.player.ribbon.PlayerRibbon
|
||||
import com.pixelized.desktop.lwa.ui.screen.campaign.player.detail.rememberTransitionAnimation
|
||||
import com.pixelized.desktop.lwa.ui.screen.campaign.player.ribbon.player.PlayerRibbon
|
||||
import com.pixelized.desktop.lwa.ui.screen.campaign.toolbar.CampaignToolbar
|
||||
import com.pixelized.desktop.lwa.ui.screen.characterSheet.detail.dialog.DiminishedStatDialog
|
||||
import com.pixelized.desktop.lwa.ui.screen.campaign.network.NetworkViewModel
|
||||
import com.pixelized.desktop.lwa.ui.screen.campaign.toolbar.CampaignToolbarViewModel
|
||||
import com.pixelized.desktop.lwa.ui.screen.characterSheet.detail.dialog.DiminishedStatDialog
|
||||
import kotlinx.coroutines.launch
|
||||
import org.koin.compose.viewmodel.koinViewModel
|
||||
|
||||
|
|
@ -53,11 +55,11 @@ val LocalCampaignLayoutScope = compositionLocalOf<CampaignLayoutScope> {
|
|||
|
||||
@Composable
|
||||
fun CampaignScreen(
|
||||
characterDetailViewModel: CharacterDetailViewModel = koinViewModel(),
|
||||
playerDetailViewModel: CharacterDetailViewModel = koinViewModel(key = "player"),
|
||||
npcDetailViewModel: CharacterDetailViewModel = koinViewModel(key = "npc"),
|
||||
characteristicDialogViewModel: CharacterDetailCharacteristicDialogViewModel = koinViewModel(),
|
||||
dismissedViewModel: CharacterDiminishedViewModel = koinViewModel(),
|
||||
campaignViewModel: CampaignToolbarViewModel = koinViewModel(),
|
||||
networkViewModel: NetworkViewModel = koinViewModel(),
|
||||
campaignChatViewModel: CampaignChatViewModel = koinViewModel(),
|
||||
) {
|
||||
val screen = LocalScreenController.current
|
||||
|
|
@ -67,7 +69,8 @@ fun CampaignScreen(
|
|||
KeyHandler {
|
||||
when {
|
||||
it.type == KeyEventType.KeyUp && it.key == Key.Escape -> {
|
||||
characterDetailViewModel.hideCharacter()
|
||||
playerDetailViewModel.hideCharacter()
|
||||
npcDetailViewModel.hideCharacter()
|
||||
true
|
||||
}
|
||||
|
||||
|
|
@ -82,7 +85,7 @@ fun CampaignScreen(
|
|||
modifier = Modifier.fillMaxSize(),
|
||||
controller = blurController
|
||||
) {
|
||||
CampaignScreenLayout(
|
||||
CampaignLayout(
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
top = {
|
||||
CampaignToolbar(
|
||||
|
|
@ -101,25 +104,50 @@ fun CampaignScreen(
|
|||
chatViewModel = campaignChatViewModel,
|
||||
)
|
||||
},
|
||||
leftOverlay = {
|
||||
leftPanel = {
|
||||
PlayerRibbon(
|
||||
modifier = Modifier.fillMaxHeight(),
|
||||
onCharacter = {
|
||||
characterDetailViewModel.showCharacter(id = it)
|
||||
playerDetailViewModel.showCharacter(id = it)
|
||||
},
|
||||
onLevelUp = {
|
||||
screen.navigateToLevelScreen(characterInstanceId = it)
|
||||
}
|
||||
)
|
||||
},
|
||||
leftOverlay = {
|
||||
CharacterDetailPanel(
|
||||
modifier = Modifier
|
||||
.padding(all = 8.dp)
|
||||
.width(width = 128.dp * 4)
|
||||
.fillMaxHeight(),
|
||||
transitionSpec = rememberTransitionAnimation(direction = LayoutDirection.Ltr),
|
||||
blurController = blurController,
|
||||
detailViewModel = npcDetailViewModel,
|
||||
characterDiminishedViewModel = dismissedViewModel,
|
||||
characteristicDialogViewModel = characteristicDialogViewModel,
|
||||
)
|
||||
},
|
||||
rightPanel = {
|
||||
NpcRibbon(
|
||||
modifier = Modifier.fillMaxHeight(),
|
||||
onCharacter = {
|
||||
npcDetailViewModel.showCharacter(id = it)
|
||||
},
|
||||
onLevelUp = {
|
||||
|
||||
}
|
||||
)
|
||||
},
|
||||
rightOverlay = {
|
||||
CharacterDetailPanel(
|
||||
modifier = Modifier
|
||||
.padding(all = 8.dp)
|
||||
.width(width = 128.dp * 4)
|
||||
.fillMaxHeight(),
|
||||
transitionSpec = rememberTransitionAnimation(direction = LayoutDirection.Rtl),
|
||||
blurController = blurController,
|
||||
detailViewModel = characterDetailViewModel,
|
||||
detailViewModel = playerDetailViewModel,
|
||||
characterDiminishedViewModel = dismissedViewModel,
|
||||
characteristicDialogViewModel = characteristicDialogViewModel,
|
||||
)
|
||||
|
|
@ -166,26 +194,34 @@ fun CampaignScreen(
|
|||
}
|
||||
|
||||
@Composable
|
||||
private fun CampaignScreenLayout(
|
||||
private fun CampaignLayout(
|
||||
modifier: Modifier = Modifier,
|
||||
top: @Composable () -> Unit,
|
||||
bottom: @Composable () -> Unit,
|
||||
main: @Composable () -> Unit,
|
||||
chat: @Composable () -> Unit,
|
||||
leftPanel: @Composable () -> Unit,
|
||||
rightPanel: @Composable () -> Unit,
|
||||
leftOverlay: @Composable () -> Unit,
|
||||
rightOverlay: @Composable () -> Unit,
|
||||
) {
|
||||
val density = LocalDensity.current
|
||||
|
||||
val leftOverlayState = remember { mutableStateOf(DpSize.Unspecified) }
|
||||
val leftPanelState = remember { mutableStateOf(DpSize.Unspecified) }
|
||||
val rightOverlayState = remember { mutableStateOf(DpSize.Unspecified) }
|
||||
val rightPanelState = remember { mutableStateOf(DpSize.Unspecified) }
|
||||
val chatOverlayState = remember { mutableStateOf(DpSize.Unspecified) }
|
||||
val scope = remember {
|
||||
CampaignLayoutScope(
|
||||
leftOverlay = leftOverlayState,
|
||||
leftPanel = leftPanelState,
|
||||
rightOverlay = rightOverlayState,
|
||||
rightPanel = rightPanelState,
|
||||
chatOverlay = chatOverlayState,
|
||||
)
|
||||
}
|
||||
|
||||
CompositionLocalProvider(
|
||||
LocalCampaignLayoutScope provides scope,
|
||||
) {
|
||||
|
|
@ -194,7 +230,7 @@ private fun CampaignScreenLayout(
|
|||
) {
|
||||
top()
|
||||
Box(
|
||||
modifier = Modifier.weight(1f, fill = true),
|
||||
modifier = Modifier.weight(weight = 1f, fill = true),
|
||||
) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
|
|
@ -210,6 +246,13 @@ private fun CampaignScreenLayout(
|
|||
) {
|
||||
chat()
|
||||
}
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.align(alignment = Alignment.CenterStart)
|
||||
.onSizeChanged { leftPanelState.value = it.toDp(density) },
|
||||
) {
|
||||
leftPanel()
|
||||
}
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.align(alignment = Alignment.CenterStart)
|
||||
|
|
@ -217,6 +260,13 @@ private fun CampaignScreenLayout(
|
|||
) {
|
||||
leftOverlay()
|
||||
}
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.align(alignment = Alignment.CenterEnd)
|
||||
.onSizeChanged { rightPanelState.value = it.toDp(density) },
|
||||
) {
|
||||
rightPanel()
|
||||
}
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.align(alignment = Alignment.CenterEnd)
|
||||
|
|
@ -233,7 +283,9 @@ private fun CampaignScreenLayout(
|
|||
@Stable
|
||||
data class CampaignLayoutScope(
|
||||
val leftOverlay: State<DpSize>,
|
||||
val leftPanel: State<DpSize>,
|
||||
val rightOverlay: State<DpSize>,
|
||||
val rightPanel: State<DpSize>,
|
||||
val chatOverlay: State<DpSize>,
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
package com.pixelized.desktop.lwa.ui.screen.campaign.player.ribbon
|
||||
package com.pixelized.desktop.lwa.ui.screen.campaign.player
|
||||
|
||||
import androidx.compose.animation.AnimatedContent
|
||||
import androidx.compose.animation.AnimatedVisibility
|
||||
|
|
@ -53,22 +53,33 @@ import kotlin.math.max
|
|||
import kotlin.math.min
|
||||
|
||||
@Stable
|
||||
data class PlayerPortraitUio(
|
||||
object CharacterPortraitDefault {
|
||||
val size = DpSize(96.dp, 128.dp)
|
||||
}
|
||||
|
||||
@Stable
|
||||
data class CharacterPortraitUio(
|
||||
val id: Campaign.CharacterInstance.Id,
|
||||
val portrait: String?,
|
||||
val name: String,
|
||||
val hp: Int,
|
||||
val maxHp: Int,
|
||||
val pp: Int,
|
||||
val maxPp: Int,
|
||||
val levelUp: Boolean,
|
||||
)
|
||||
val enableDetail: Boolean,
|
||||
val stats: StatsDetail?,
|
||||
) {
|
||||
@Stable
|
||||
data class StatsDetail(
|
||||
val hp: Int,
|
||||
val maxHp: Int,
|
||||
val pp: Int,
|
||||
val maxPp: Int,
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun PlayerPortrait(
|
||||
fun CharacterPortrait(
|
||||
modifier: Modifier = Modifier,
|
||||
size: DpSize,
|
||||
character: PlayerPortraitUio,
|
||||
size: DpSize = CharacterPortraitDefault.size,
|
||||
character: CharacterPortraitUio,
|
||||
onCharacter: (id: Campaign.CharacterInstance.Id) -> Unit,
|
||||
onLevelUp: (id: Campaign.CharacterInstance.Id) -> Unit,
|
||||
) {
|
||||
|
|
@ -77,9 +88,9 @@ fun PlayerPortrait(
|
|||
Box(
|
||||
modifier = modifier
|
||||
.size(size = size)
|
||||
.clip(shape = remember { RoundedCornerShape(8.dp) })
|
||||
.clip(shape = MaterialTheme.lwa.shapes.portrait)
|
||||
.background(color = colorScheme.elevated.base1dp)
|
||||
.clickable { onCharacter(character.id) },
|
||||
.clickable(character.enableDetail) { onCharacter(character.id) },
|
||||
) {
|
||||
AnimatedContent(
|
||||
targetState = character.portrait,
|
||||
|
|
@ -95,11 +106,6 @@ fun PlayerPortrait(
|
|||
)
|
||||
}
|
||||
|
||||
BloodOverlay(
|
||||
maxHp = character.maxHp.toFloat(),
|
||||
hp = character.hp.toFloat(),
|
||||
)
|
||||
|
||||
AnimatedVisibility(
|
||||
modifier = Modifier.offset(x = (-8).dp, y = (-8).dp),
|
||||
visible = character.levelUp,
|
||||
|
|
@ -115,59 +121,67 @@ fun PlayerPortrait(
|
|||
}
|
||||
}
|
||||
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.drawWithContent {
|
||||
drawRect(brush = colorScheme.portraitBackgroundBrush)
|
||||
drawContent()
|
||||
character.stats?.let { stats ->
|
||||
|
||||
BloodOverlay(
|
||||
maxHp = stats.maxHp.toFloat(),
|
||||
hp = stats.hp.toFloat(),
|
||||
)
|
||||
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.drawWithContent {
|
||||
drawRect(brush = colorScheme.portraitBackgroundBrush)
|
||||
drawContent()
|
||||
}
|
||||
.padding(vertical = 2.dp, horizontal = 4.dp),
|
||||
verticalArrangement = Arrangement.aligned(alignment = Alignment.Bottom),
|
||||
) {
|
||||
Row {
|
||||
Icon(
|
||||
modifier = Modifier.size(12.dp).offset(y = 3.dp),
|
||||
painter = painterResource(Res.drawable.ic_heart_24dp),
|
||||
contentDescription = null
|
||||
)
|
||||
Spacer(
|
||||
modifier = Modifier.width(width = 2.dp),
|
||||
)
|
||||
Text(
|
||||
modifier = Modifier.alignByBaseline(),
|
||||
style = MaterialTheme.typography.caption,
|
||||
fontWeight = FontWeight.Bold,
|
||||
text = "${stats.hp}",
|
||||
)
|
||||
Text(
|
||||
modifier = Modifier.alignByBaseline(),
|
||||
style = MaterialTheme.typography.caption,
|
||||
fontWeight = FontWeight.Light,
|
||||
text = "/${stats.maxHp}",
|
||||
)
|
||||
}
|
||||
Row {
|
||||
Icon(
|
||||
modifier = Modifier.size(12.dp).offset(y = 2.dp),
|
||||
painter = painterResource(Res.drawable.ic_water_drop_24dp),
|
||||
contentDescription = null
|
||||
)
|
||||
Spacer(
|
||||
modifier = Modifier.width(width = 2.dp),
|
||||
)
|
||||
Text(
|
||||
modifier = Modifier.alignByBaseline(),
|
||||
style = MaterialTheme.typography.caption,
|
||||
fontWeight = FontWeight.Bold,
|
||||
text = "${stats.pp}",
|
||||
)
|
||||
Text(
|
||||
modifier = Modifier.alignByBaseline(),
|
||||
fontWeight = FontWeight.Light,
|
||||
style = MaterialTheme.typography.caption,
|
||||
text = "/${stats.maxPp}",
|
||||
)
|
||||
}
|
||||
.padding(vertical = 2.dp, horizontal = 4.dp),
|
||||
verticalArrangement = Arrangement.aligned(alignment = Alignment.Bottom),
|
||||
) {
|
||||
Row {
|
||||
Icon(
|
||||
modifier = Modifier.size(12.dp).offset(y = 3.dp),
|
||||
painter = painterResource(Res.drawable.ic_heart_24dp),
|
||||
contentDescription = null
|
||||
)
|
||||
Spacer(
|
||||
modifier = Modifier.width(width = 2.dp),
|
||||
)
|
||||
Text(
|
||||
modifier = Modifier.alignByBaseline(),
|
||||
style = MaterialTheme.typography.caption,
|
||||
fontWeight = FontWeight.Bold,
|
||||
text = "${character.hp}",
|
||||
)
|
||||
Text(
|
||||
modifier = Modifier.alignByBaseline(),
|
||||
style = MaterialTheme.typography.caption,
|
||||
fontWeight = FontWeight.Light,
|
||||
text = "/${character.maxHp}",
|
||||
)
|
||||
}
|
||||
Row {
|
||||
Icon(
|
||||
modifier = Modifier.size(12.dp).offset(y = 2.dp),
|
||||
painter = painterResource(Res.drawable.ic_water_drop_24dp),
|
||||
contentDescription = null
|
||||
)
|
||||
Spacer(
|
||||
modifier = Modifier.width(width = 2.dp),
|
||||
)
|
||||
Text(
|
||||
modifier = Modifier.alignByBaseline(),
|
||||
style = MaterialTheme.typography.caption,
|
||||
fontWeight = FontWeight.Bold,
|
||||
text = "${character.pp}",
|
||||
)
|
||||
Text(
|
||||
modifier = Modifier.alignByBaseline(),
|
||||
fontWeight = FontWeight.Light,
|
||||
style = MaterialTheme.typography.caption,
|
||||
text = "/${character.maxPp}",
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -184,7 +198,7 @@ private fun BloodOverlay(
|
|||
targetValue = min(maxHp, max(0f, (maxHp - hp) / maxHp)),
|
||||
animationSpec = tween(durationMillis = 350, easing = EaseOutCirc)
|
||||
)
|
||||
val animatedColor = animateColorAsState(
|
||||
val animatedColor = animateColorAsState(
|
||||
targetValue = bloodColor.copy(alpha = ((maxHp - hp) / maxHp) / 4f + .25f)
|
||||
)
|
||||
Box(
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package com.pixelized.desktop.lwa.ui.screen.campaign.player.ribbon
|
||||
package com.pixelized.desktop.lwa.ui.screen.campaign.player
|
||||
|
||||
import androidx.compose.animation.AnimatedContent
|
||||
import androidx.compose.animation.SizeTransform
|
||||
|
|
@ -37,20 +37,21 @@ import androidx.compose.ui.text.font.FontWeight
|
|||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.unit.DpSize
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.pixelized.shared.lwa.model.campaign.Campaign
|
||||
import kotlinx.coroutines.launch
|
||||
import lwacharactersheet.composeapp.generated.resources.Res
|
||||
import lwacharactersheet.composeapp.generated.resources.ic_d20_24dp
|
||||
import org.jetbrains.compose.resources.painterResource
|
||||
|
||||
@Stable
|
||||
data class PlayerPortraitRollUio(
|
||||
val characterId: String,
|
||||
data class CharacterPortraitRollUio(
|
||||
val characterId: Campaign.CharacterInstance.Id,
|
||||
val value: Int?,
|
||||
val label: String?,
|
||||
)
|
||||
|
||||
@Stable
|
||||
data class PlayerPortraitRollAnimation(
|
||||
data class CharacterPortraitRollAnimation(
|
||||
val alpha: Animatable<Float, AnimationVector1D> = Animatable(0f),
|
||||
val rotation: Animatable<Float, AnimationVector1D> = Animatable(0f),
|
||||
val scale: Animatable<Float, AnimationVector1D> = Animatable(1f),
|
||||
|
|
@ -58,12 +59,12 @@ data class PlayerPortraitRollAnimation(
|
|||
|
||||
@OptIn(ExperimentalFoundationApi::class)
|
||||
@Composable
|
||||
fun PlayerPortraitRoll(
|
||||
fun CharacterPortraitRoll(
|
||||
modifier: Modifier = Modifier,
|
||||
size: DpSize,
|
||||
value: PlayerPortraitRollUio?,
|
||||
onLeftClick: (PlayerPortraitRollUio) -> Unit,
|
||||
onRightClick: (PlayerPortraitRollUio) -> Unit,
|
||||
size: DpSize = CharacterPortraitDefault.size,
|
||||
value: CharacterPortraitRollUio?,
|
||||
onLeftClick: (CharacterPortraitRollUio) -> Unit,
|
||||
onRightClick: (CharacterPortraitRollUio) -> Unit,
|
||||
) {
|
||||
AnimatedContent(
|
||||
modifier = modifier
|
||||
|
|
@ -133,9 +134,9 @@ fun PlayerPortraitRoll(
|
|||
}
|
||||
|
||||
@Composable
|
||||
private fun diceIconAnimation(key: Any = Unit): PlayerPortraitRollAnimation {
|
||||
private fun diceIconAnimation(key: Any = Unit): CharacterPortraitRollAnimation {
|
||||
val animation = remember(key) {
|
||||
PlayerPortraitRollAnimation()
|
||||
CharacterPortraitRollAnimation()
|
||||
}
|
||||
LaunchedEffect(key) {
|
||||
launch {
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package com.pixelized.desktop.lwa.ui.screen.campaign.player.ribbon
|
||||
package com.pixelized.desktop.lwa.ui.screen.campaign.player
|
||||
|
||||
import com.pixelized.shared.lwa.model.AlteredCharacterSheetFactory
|
||||
import com.pixelized.shared.lwa.model.alteration.FieldAlteration
|
||||
|
|
@ -7,15 +7,17 @@ import com.pixelized.shared.lwa.model.campaign.damage
|
|||
import com.pixelized.shared.lwa.model.campaign.power
|
||||
import com.pixelized.shared.lwa.model.characterSheet.CharacterSheet
|
||||
|
||||
class PlayerRibbonFactory(
|
||||
class CharacterRibbonFactory(
|
||||
private val alteredCharacterSheetFactory: AlteredCharacterSheetFactory,
|
||||
) {
|
||||
fun convertToPlayerPortraitUio(
|
||||
characterSheet: CharacterSheet?,
|
||||
alterations: Map<String, List<FieldAlteration>>,
|
||||
characterInstanceId: Campaign.CharacterInstance.Id,
|
||||
characterInstance: Campaign.CharacterInstance,
|
||||
alterations: Map<String, List<FieldAlteration>>,
|
||||
): PlayerPortraitUio? {
|
||||
enableDetail: Boolean,
|
||||
displayCharacterStats: Boolean,
|
||||
): CharacterPortraitUio? {
|
||||
if (characterSheet == null) return null
|
||||
|
||||
val alteredCharacterSheet = alteredCharacterSheetFactory.sheet(
|
||||
|
|
@ -23,15 +25,20 @@ class PlayerRibbonFactory(
|
|||
alterations = alterations,
|
||||
)
|
||||
|
||||
return PlayerPortraitUio(
|
||||
return CharacterPortraitUio(
|
||||
id = characterInstanceId,
|
||||
portrait = alteredCharacterSheet.thumbnail,
|
||||
name = alteredCharacterSheet.name,
|
||||
hp = alteredCharacterSheet.maxHp - characterInstance.damage,
|
||||
maxHp = alteredCharacterSheet.maxHp,
|
||||
pp = alteredCharacterSheet.maxPp - characterInstance.power,
|
||||
maxPp = alteredCharacterSheet.maxPp,
|
||||
levelUp = alteredCharacterSheet.shouldLevelUp,
|
||||
enableDetail = enableDetail,
|
||||
stats = takeIf { displayCharacterStats }?.let {
|
||||
CharacterPortraitUio.StatsDetail(
|
||||
hp = alteredCharacterSheet.maxHp - characterInstance.damage,
|
||||
maxHp = alteredCharacterSheet.maxHp,
|
||||
pp = alteredCharacterSheet.maxPp - characterInstance.power,
|
||||
maxPp = alteredCharacterSheet.maxPp,
|
||||
)
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,6 +1,8 @@
|
|||
package com.pixelized.desktop.lwa.ui.screen.campaign.player.detail
|
||||
|
||||
import androidx.compose.animation.AnimatedContent
|
||||
import androidx.compose.animation.AnimatedContentTransitionScope
|
||||
import androidx.compose.animation.ContentTransform
|
||||
import androidx.compose.animation.EnterTransition
|
||||
import androidx.compose.animation.ExitTransition
|
||||
import androidx.compose.animation.fadeIn
|
||||
|
|
@ -22,9 +24,11 @@ import androidx.compose.runtime.Composable
|
|||
import androidx.compose.runtime.Stable
|
||||
import androidx.compose.runtime.State
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Shape
|
||||
import androidx.compose.ui.unit.LayoutDirection
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.pixelized.desktop.lwa.LocalRollHostState
|
||||
import com.pixelized.desktop.lwa.ui.composable.blur.BlurContentController
|
||||
|
|
@ -52,6 +56,7 @@ data class CharacterDetailPanelUio(
|
|||
fun CharacterDetailPanel(
|
||||
modifier: Modifier = Modifier,
|
||||
blurController: BlurContentController,
|
||||
transitionSpec: AnimatedContentTransitionScope<CharacterDetailPanelUio>.() -> ContentTransform = rememberTransitionAnimation(),
|
||||
detailViewModel: CharacterDetailViewModel,
|
||||
characteristicDialogViewModel: CharacterDetailCharacteristicDialogViewModel,
|
||||
characterDiminishedViewModel: CharacterDiminishedViewModel,
|
||||
|
|
@ -63,6 +68,7 @@ fun CharacterDetailPanel(
|
|||
CharacterDetailAnimatedPanel(
|
||||
modifier = modifier,
|
||||
detail = detail,
|
||||
transitionSpec = transitionSpec,
|
||||
onDismissRequest = {
|
||||
detailViewModel.hideCharacter()
|
||||
},
|
||||
|
|
@ -125,6 +131,7 @@ fun CharacterDetailPanel(
|
|||
fun CharacterDetailAnimatedPanel(
|
||||
modifier: Modifier = Modifier,
|
||||
detail: State<CharacterDetailPanelUio>,
|
||||
transitionSpec: AnimatedContentTransitionScope<CharacterDetailPanelUio>.() -> ContentTransform,
|
||||
onDismissRequest: (id: Campaign.CharacterInstance.Id) -> Unit,
|
||||
onDiminished: (id: Campaign.CharacterInstance.Id) -> Unit,
|
||||
onHp: (id: Campaign.CharacterInstance.Id) -> Unit,
|
||||
|
|
@ -140,15 +147,7 @@ fun CharacterDetailAnimatedPanel(
|
|||
AnimatedContent(
|
||||
modifier = Modifier.matchParentSize(),
|
||||
targetState = detail.value,
|
||||
transitionSpec = {
|
||||
if (initialState.characterInstanceId != targetState.characterInstanceId) {
|
||||
val enter = fadeIn() + slideInHorizontally { it / 2 }
|
||||
val exit = fadeOut() + slideOutHorizontally { it / 2 }
|
||||
enter togetherWith exit
|
||||
} else {
|
||||
EnterTransition.None togetherWith ExitTransition.None
|
||||
}
|
||||
}
|
||||
transitionSpec = transitionSpec,
|
||||
) {
|
||||
when {
|
||||
it.characterInstanceId == null -> Box(
|
||||
|
|
@ -226,3 +225,22 @@ fun CharacterDetailContent(
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
@Stable
|
||||
fun rememberTransitionAnimation(
|
||||
direction: LayoutDirection = LayoutDirection.Rtl,
|
||||
) : AnimatedContentTransitionScope<CharacterDetailPanelUio>.() -> ContentTransform {
|
||||
return remember {
|
||||
val mul = if (direction == LayoutDirection.Rtl) 1 else -1
|
||||
{
|
||||
if (initialState.characterInstanceId != targetState.characterInstanceId) {
|
||||
val enter = fadeIn() + slideInHorizontally { mul * it / 2 }
|
||||
val exit = fadeOut() + slideOutHorizontally { mul * it / 2 }
|
||||
enter togetherWith exit
|
||||
} else {
|
||||
EnterTransition.None togetherWith ExitTransition.None
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -31,7 +31,7 @@ class CharacterDetailViewModel(
|
|||
CharacterDetailPanelUio(
|
||||
characterInstanceId = characterInstanceId,
|
||||
header = combine(
|
||||
campaignRepository.characterInstanceFlow(id = characterInstanceId),
|
||||
campaignRepository.instanceFlow(characterInstanceId = characterInstanceId),
|
||||
characterSheetRepository.characterDetailFlow(characterSheetId = characterInstanceId.characterSheetId),
|
||||
alterationRepository.alterationsFlow(characterInstanceId = characterInstanceId),
|
||||
) { characterInstance, characterSheet, alterations ->
|
||||
|
|
@ -47,7 +47,7 @@ class CharacterDetailViewModel(
|
|||
initialValue = null,
|
||||
),
|
||||
sheet = combine(
|
||||
campaignRepository.characterInstanceFlow(id = characterInstanceId),
|
||||
campaignRepository.instanceFlow(characterInstanceId = characterInstanceId),
|
||||
characterSheetRepository.characterDetailFlow(characterSheetId = characterInstanceId.characterSheetId),
|
||||
alterationRepository.alterationsFlow(characterInstanceId = characterInstanceId),
|
||||
) { characterInstance, characterSheet, alterations ->
|
||||
|
|
|
|||
|
|
@ -57,6 +57,7 @@ class CharacterDiminishedViewModel(
|
|||
val diminished = dialog.value().text.toIntOrNull() ?: 0
|
||||
networkRepository.share(
|
||||
payload = CampaignMessage.UpdateDiminished(
|
||||
prefix = dialog.characterInstanceId.prefix,
|
||||
characterSheetId = dialog.characterInstanceId.characterSheetId,
|
||||
instanceId = dialog.characterInstanceId.instanceId,
|
||||
diminished = diminished,
|
||||
|
|
|
|||
|
|
@ -1,88 +0,0 @@
|
|||
package com.pixelized.desktop.lwa.ui.screen.campaign.player.ribbon
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.MutableState
|
||||
import androidx.compose.runtime.Stable
|
||||
import androidx.compose.runtime.State
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
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.desktop.lwa.repository.roll_history.RollHistoryRepository
|
||||
import com.pixelized.desktop.lwa.repository.settings.SettingsRepository
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.flow.SharingStarted
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.combine
|
||||
import kotlinx.coroutines.flow.flatMapMerge
|
||||
import kotlinx.coroutines.flow.stateIn
|
||||
import java.text.Collator
|
||||
|
||||
class PlayerRibbonViewModel(
|
||||
private val rollHistoryRepository: RollHistoryRepository,
|
||||
private val settingsRepository: SettingsRepository,
|
||||
characterRepository: CharacterSheetRepository,
|
||||
alterationRepository: AlterationRepository,
|
||||
private val ribbonFactory: PlayerRibbonFactory,
|
||||
campaignRepository: CampaignRepository,
|
||||
) : ViewModel() {
|
||||
|
||||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
val characters: StateFlow<List<PlayerPortraitUio>> = campaignRepository.campaignFlow
|
||||
.flatMapMerge { campaign ->
|
||||
combine<PlayerPortraitUio?, List<PlayerPortraitUio>>(
|
||||
flows = campaign.characters.map { entry ->
|
||||
combine(
|
||||
characterRepository.characterDetailFlow(characterSheetId = entry.key.characterSheetId),
|
||||
alterationRepository.alterationsFlow(characterInstanceId = entry.key),
|
||||
) { sheet, alterations ->
|
||||
ribbonFactory.convertToPlayerPortraitUio(
|
||||
characterSheet = sheet,
|
||||
characterInstanceId = entry.key,
|
||||
characterInstance = entry.value,
|
||||
alterations = alterations,
|
||||
)
|
||||
}
|
||||
},
|
||||
transform = { headers ->
|
||||
headers.mapNotNull { it }
|
||||
.sortedWith(compareBy(Collator.getInstance()) { it.name })
|
||||
.toList()
|
||||
}
|
||||
)
|
||||
}
|
||||
.stateIn(
|
||||
scope = viewModelScope,
|
||||
started = SharingStarted.Eagerly,
|
||||
initialValue = emptyList()
|
||||
)
|
||||
|
||||
private val rolls = hashMapOf<String, MutableState<PlayerPortraitRollUio?>>()
|
||||
|
||||
@Composable
|
||||
@Stable
|
||||
fun roll(characterSheetId: String): State<PlayerPortraitRollUio?> {
|
||||
val state = rolls.getOrPut(characterSheetId) { mutableStateOf(null) }
|
||||
LaunchedEffect(characterSheetId) {
|
||||
rollHistoryRepository.rolls.collect { roll ->
|
||||
if (settingsRepository.settings().dynamicDice) {
|
||||
if (roll.characterSheetId == characterSheetId) {
|
||||
state.value = PlayerPortraitRollUio(
|
||||
characterId = characterSheetId,
|
||||
value = roll.rollValue,
|
||||
label = roll.resultLabel?.split(" ")?.joinToString(separator = "\n") { it }
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return state
|
||||
}
|
||||
|
||||
fun onPortraitRollRightClick(characterId: String) {
|
||||
rolls[characterId]?.value = null
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,57 @@
|
|||
package com.pixelized.desktop.lwa.ui.screen.campaign.player.ribbon.npc
|
||||
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.PaddingValues
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.items
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.pixelized.desktop.lwa.ui.screen.campaign.player.CharacterPortrait
|
||||
import com.pixelized.desktop.lwa.ui.screen.campaign.player.CharacterPortraitDefault
|
||||
import com.pixelized.desktop.lwa.ui.screen.campaign.player.CharacterPortraitRoll
|
||||
import com.pixelized.shared.lwa.model.campaign.Campaign
|
||||
import org.koin.compose.viewmodel.koinViewModel
|
||||
|
||||
@Composable
|
||||
fun NpcRibbon(
|
||||
modifier: Modifier = Modifier,
|
||||
viewModel: NpcRibbonViewModel = koinViewModel(),
|
||||
padding: PaddingValues = PaddingValues(all = 8.dp),
|
||||
onCharacter: (id: Campaign.CharacterInstance.Id) -> Unit,
|
||||
onLevelUp: (id: Campaign.CharacterInstance.Id) -> Unit,
|
||||
) {
|
||||
val characters = viewModel.characters.collectAsState()
|
||||
|
||||
LazyColumn(
|
||||
modifier = modifier,
|
||||
contentPadding = padding,
|
||||
verticalArrangement = Arrangement.spacedBy(space = 8.dp)
|
||||
) {
|
||||
items(
|
||||
items = characters.value,
|
||||
key = { it.id },
|
||||
) {
|
||||
Row {
|
||||
CharacterPortraitRoll(
|
||||
size = CharacterPortraitDefault.size,
|
||||
value = viewModel.roll(characterId = it.id).value,
|
||||
onRightClick = {
|
||||
viewModel.onPortraitRollRightClick(characterId = it.characterId)
|
||||
},
|
||||
onLeftClick = {
|
||||
|
||||
},
|
||||
)
|
||||
CharacterPortrait(
|
||||
size = CharacterPortraitDefault.size,
|
||||
character = it,
|
||||
onCharacter = onCharacter,
|
||||
onLevelUp = onLevelUp,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,114 @@
|
|||
package com.pixelized.desktop.lwa.ui.screen.campaign.player.ribbon.npc
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.MutableState
|
||||
import androidx.compose.runtime.Stable
|
||||
import androidx.compose.runtime.State
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
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.desktop.lwa.repository.roll_history.RollHistoryRepository
|
||||
import com.pixelized.desktop.lwa.repository.settings.SettingsRepository
|
||||
import com.pixelized.desktop.lwa.ui.screen.campaign.player.CharacterPortraitRollUio
|
||||
import com.pixelized.desktop.lwa.ui.screen.campaign.player.CharacterPortraitUio
|
||||
import com.pixelized.desktop.lwa.ui.screen.campaign.player.CharacterRibbonFactory
|
||||
import com.pixelized.shared.lwa.model.campaign.Campaign
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.flow.SharingStarted
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.combine
|
||||
import kotlinx.coroutines.flow.flatMapMerge
|
||||
import kotlinx.coroutines.flow.flowOf
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.stateIn
|
||||
import java.text.Collator
|
||||
|
||||
private typealias CharacterId = Campaign.CharacterInstance.Id
|
||||
|
||||
class NpcRibbonViewModel(
|
||||
private val rollHistoryRepository: RollHistoryRepository,
|
||||
private val settingsRepository: SettingsRepository,
|
||||
characterRepository: CharacterSheetRepository,
|
||||
alterationRepository: AlterationRepository,
|
||||
campaignRepository: CampaignRepository,
|
||||
private val ribbonFactory: CharacterRibbonFactory,
|
||||
) : ViewModel() {
|
||||
private val rolls = hashMapOf<CharacterId, MutableState<CharacterPortraitRollUio?>>()
|
||||
|
||||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
val characters: StateFlow<List<CharacterPortraitUio>> = campaignRepository.campaignFlow
|
||||
.flatMapMerge { campaign ->
|
||||
if (campaign.npcs.isEmpty()) {
|
||||
flowOf(emptyList())
|
||||
} else {
|
||||
combine<CharacterPortraitUio?, List<CharacterPortraitUio>>(
|
||||
flows = campaign.npcs.map { entry ->
|
||||
combine(
|
||||
characterRepository.characterDetailFlow(characterSheetId = entry.key.characterSheetId),
|
||||
alterationRepository.alterationsFlow(characterInstanceId = entry.key),
|
||||
) { sheet, alterations ->
|
||||
ribbonFactory.convertToPlayerPortraitUio(
|
||||
characterSheet = sheet,
|
||||
alterations = alterations,
|
||||
characterInstanceId = entry.key,
|
||||
characterInstance = entry.value,
|
||||
enableDetail = settingsRepository.settings().isGM,
|
||||
displayCharacterStats = settingsRepository.settings().isGM,
|
||||
)
|
||||
}
|
||||
},
|
||||
transform = { headers ->
|
||||
headers.mapNotNull { it }
|
||||
.sortedWith(compareBy(Collator.getInstance()) { it.name })
|
||||
.toList()
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
.stateIn(
|
||||
scope = viewModelScope,
|
||||
started = SharingStarted.Eagerly,
|
||||
initialValue = emptyList()
|
||||
)
|
||||
|
||||
@Composable
|
||||
@Stable
|
||||
fun roll(
|
||||
characterId: Campaign.CharacterInstance.Id,
|
||||
): State<CharacterPortraitRollUio?> {
|
||||
val state = rolls.getOrPut(characterId) { mutableStateOf(null) }
|
||||
|
||||
LaunchedEffect(characterId) {
|
||||
combine(
|
||||
settingsRepository.settingsFlow(),
|
||||
rollHistoryRepository.rolls,
|
||||
) { settings, roll ->
|
||||
if (settings.dynamicDice &&
|
||||
characterId.equals(roll.prefix, roll.characterSheetId, roll.instanceId)
|
||||
) {
|
||||
state.value = CharacterPortraitRollUio(
|
||||
characterId = Campaign.CharacterInstance.Id(
|
||||
prefix = characterId.prefix,
|
||||
characterSheetId = characterId.characterSheetId,
|
||||
instanceId = characterId.instanceId,
|
||||
),
|
||||
value = roll.rollValue,
|
||||
label = roll.resultLabel?.split(" ")?.joinToString(separator = "\n") { it }
|
||||
)
|
||||
}
|
||||
}.launchIn(this)
|
||||
}
|
||||
|
||||
return state
|
||||
}
|
||||
|
||||
fun onPortraitRollRightClick(
|
||||
characterId: Campaign.CharacterInstance.Id,
|
||||
) {
|
||||
rolls[characterId]?.value = null
|
||||
}
|
||||
}
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package com.pixelized.desktop.lwa.ui.screen.campaign.player.ribbon
|
||||
package com.pixelized.desktop.lwa.ui.screen.campaign.player.ribbon.player
|
||||
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.PaddingValues
|
||||
|
|
@ -8,26 +8,21 @@ import androidx.compose.foundation.lazy.items
|
|||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.unit.DpSize
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.pixelized.desktop.lwa.ui.screen.campaign.player.CharacterPortrait
|
||||
import com.pixelized.desktop.lwa.ui.screen.campaign.player.CharacterPortraitRoll
|
||||
import com.pixelized.shared.lwa.model.campaign.Campaign
|
||||
import org.koin.compose.viewmodel.koinViewModel
|
||||
|
||||
object PlayerRibbon {
|
||||
object Default {
|
||||
val size = DpSize(96.dp, 128.dp)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun PlayerRibbon(
|
||||
modifier: Modifier = Modifier,
|
||||
playerRibbonViewModel: PlayerRibbonViewModel = koinViewModel(),
|
||||
viewModel: PlayerRibbonViewModel = koinViewModel(),
|
||||
padding: PaddingValues = PaddingValues(all = 8.dp),
|
||||
onCharacter: (id: Campaign.CharacterInstance.Id) -> Unit,
|
||||
onLevelUp: (id: Campaign.CharacterInstance.Id) -> Unit,
|
||||
) {
|
||||
val characters = playerRibbonViewModel.characters.collectAsState()
|
||||
val characters = viewModel.characters.collectAsState()
|
||||
|
||||
LazyColumn(
|
||||
modifier = modifier,
|
||||
|
|
@ -39,17 +34,15 @@ fun PlayerRibbon(
|
|||
key = { it.id },
|
||||
) {
|
||||
Row {
|
||||
PlayerPortrait(
|
||||
size = PlayerRibbon.Default.size,
|
||||
CharacterPortrait(
|
||||
character = it,
|
||||
onCharacter = onCharacter,
|
||||
onLevelUp = onLevelUp,
|
||||
)
|
||||
PlayerPortraitRoll(
|
||||
size = PlayerRibbon.Default.size,
|
||||
value = playerRibbonViewModel.roll(characterSheetId = it.id.characterSheetId).value,
|
||||
CharacterPortraitRoll(
|
||||
value = viewModel.roll(characterId = it.id).value,
|
||||
onRightClick = {
|
||||
playerRibbonViewModel.onPortraitRollRightClick(characterId = it.characterId)
|
||||
viewModel.onPortraitRollRightClick(characterId = it.characterId)
|
||||
},
|
||||
onLeftClick = {
|
||||
|
||||
|
|
@ -0,0 +1,115 @@
|
|||
package com.pixelized.desktop.lwa.ui.screen.campaign.player.ribbon.player
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.MutableState
|
||||
import androidx.compose.runtime.Stable
|
||||
import androidx.compose.runtime.State
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
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.desktop.lwa.repository.roll_history.RollHistoryRepository
|
||||
import com.pixelized.desktop.lwa.repository.settings.SettingsRepository
|
||||
import com.pixelized.desktop.lwa.ui.screen.campaign.player.CharacterPortraitRollUio
|
||||
import com.pixelized.desktop.lwa.ui.screen.campaign.player.CharacterPortraitUio
|
||||
import com.pixelized.desktop.lwa.ui.screen.campaign.player.CharacterRibbonFactory
|
||||
import com.pixelized.shared.lwa.model.campaign.Campaign
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.flow.SharingStarted
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.combine
|
||||
import kotlinx.coroutines.flow.flatMapMerge
|
||||
import kotlinx.coroutines.flow.flowOf
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.stateIn
|
||||
import java.text.Collator
|
||||
|
||||
private typealias CharacterId = Campaign.CharacterInstance.Id
|
||||
|
||||
class PlayerRibbonViewModel(
|
||||
private val rollHistoryRepository: RollHistoryRepository,
|
||||
private val settingsRepository: SettingsRepository,
|
||||
characterRepository: CharacterSheetRepository,
|
||||
alterationRepository: AlterationRepository,
|
||||
campaignRepository: CampaignRepository,
|
||||
private val ribbonFactory: CharacterRibbonFactory,
|
||||
) : ViewModel() {
|
||||
|
||||
private val rolls = hashMapOf<CharacterId, MutableState<CharacterPortraitRollUio?>>()
|
||||
|
||||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
val characters: StateFlow<List<CharacterPortraitUio>> = campaignRepository.campaignFlow
|
||||
.flatMapMerge { campaign ->
|
||||
if (campaign.characters.isEmpty()) {
|
||||
flowOf(emptyList())
|
||||
} else {
|
||||
combine<CharacterPortraitUio?, List<CharacterPortraitUio>>(
|
||||
flows = campaign.characters.map { entry ->
|
||||
combine(
|
||||
characterRepository.characterDetailFlow(characterSheetId = entry.key.characterSheetId),
|
||||
alterationRepository.alterationsFlow(characterInstanceId = entry.key),
|
||||
) { sheet, alterations ->
|
||||
ribbonFactory.convertToPlayerPortraitUio(
|
||||
characterSheet = sheet,
|
||||
alterations = alterations,
|
||||
characterInstanceId = entry.key,
|
||||
characterInstance = entry.value,
|
||||
enableDetail = true,
|
||||
displayCharacterStats = true,
|
||||
)
|
||||
}
|
||||
},
|
||||
transform = { headers ->
|
||||
headers.mapNotNull { it }
|
||||
.sortedWith(compareBy(Collator.getInstance()) { it.name })
|
||||
.toList()
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
.stateIn(
|
||||
scope = viewModelScope,
|
||||
started = SharingStarted.Eagerly,
|
||||
initialValue = emptyList()
|
||||
)
|
||||
|
||||
@Composable
|
||||
@Stable
|
||||
fun roll(
|
||||
characterId: Campaign.CharacterInstance.Id,
|
||||
): State<CharacterPortraitRollUio?> {
|
||||
val state = rolls.getOrPut(characterId) { mutableStateOf(null) }
|
||||
|
||||
LaunchedEffect(characterId) {
|
||||
combine(
|
||||
settingsRepository.settingsFlow(),
|
||||
rollHistoryRepository.rolls,
|
||||
) { settings, roll ->
|
||||
if (settings.dynamicDice &&
|
||||
characterId.equals(roll.prefix, roll.characterSheetId, roll.instanceId)
|
||||
) {
|
||||
state.value = CharacterPortraitRollUio(
|
||||
characterId = Campaign.CharacterInstance.Id(
|
||||
prefix = characterId.prefix,
|
||||
characterSheetId = characterId.characterSheetId,
|
||||
instanceId = characterId.instanceId,
|
||||
),
|
||||
value = roll.rollValue,
|
||||
label = roll.resultLabel?.split(" ")?.joinToString(separator = "\n") { it }
|
||||
)
|
||||
}
|
||||
}.launchIn(this)
|
||||
}
|
||||
|
||||
return state
|
||||
}
|
||||
|
||||
fun onPortraitRollRightClick(
|
||||
characterId: Campaign.CharacterInstance.Id,
|
||||
) {
|
||||
rolls[characterId]?.value = null
|
||||
}
|
||||
}
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package com.pixelized.desktop.lwa.ui.screen.campaign.chat
|
||||
package com.pixelized.desktop.lwa.ui.screen.campaign.text
|
||||
|
||||
import androidx.compose.animation.core.animateDpAsState
|
||||
import androidx.compose.foundation.background
|
||||
|
|
@ -36,14 +36,14 @@ import com.pixelized.desktop.lwa.repository.settings.model.Settings
|
|||
import com.pixelized.desktop.lwa.ui.navigation.window.LocalWindowState
|
||||
import com.pixelized.desktop.lwa.ui.screen.campaign.CampaignLayoutScope
|
||||
import com.pixelized.desktop.lwa.ui.screen.campaign.LocalCampaignLayoutScope
|
||||
import com.pixelized.desktop.lwa.ui.screen.campaign.chat.text.CharacteristicTextMessage
|
||||
import com.pixelized.desktop.lwa.ui.screen.campaign.chat.text.CharacteristicTextMessageUio
|
||||
import com.pixelized.desktop.lwa.ui.screen.campaign.chat.text.DiminishedTextMessage
|
||||
import com.pixelized.desktop.lwa.ui.screen.campaign.chat.text.DiminishedTextMessageUio
|
||||
import com.pixelized.desktop.lwa.ui.screen.campaign.chat.text.RollTextMessage
|
||||
import com.pixelized.desktop.lwa.ui.screen.campaign.chat.text.RollTextMessageUio
|
||||
import com.pixelized.desktop.lwa.ui.screen.campaign.chat.text.TextMessage
|
||||
import com.pixelized.desktop.lwa.ui.screen.campaign.player.ribbon.PlayerRibbon
|
||||
import com.pixelized.desktop.lwa.ui.screen.campaign.player.CharacterPortraitDefault
|
||||
import com.pixelized.desktop.lwa.ui.screen.campaign.text.messages.CharacteristicTextMessage
|
||||
import com.pixelized.desktop.lwa.ui.screen.campaign.text.messages.CharacteristicTextMessageUio
|
||||
import com.pixelized.desktop.lwa.ui.screen.campaign.text.messages.DiminishedTextMessage
|
||||
import com.pixelized.desktop.lwa.ui.screen.campaign.text.messages.DiminishedTextMessageUio
|
||||
import com.pixelized.desktop.lwa.ui.screen.campaign.text.messages.RollTextMessage
|
||||
import com.pixelized.desktop.lwa.ui.screen.campaign.text.messages.RollTextMessageUio
|
||||
import com.pixelized.desktop.lwa.ui.screen.campaign.text.messages.TextMessage
|
||||
import com.pixelized.desktop.lwa.ui.theme.lwa
|
||||
import kotlinx.coroutines.launch
|
||||
import org.koin.compose.viewmodel.koinViewModel
|
||||
|
|
@ -73,7 +73,7 @@ fun CampaignChat(
|
|||
modifier = modifier
|
||||
.size(
|
||||
width = animatedChatWidth.value,
|
||||
height = PlayerRibbon.Default.size.height * 2 + 8.dp,
|
||||
height = CharacterPortraitDefault.size.height * 2 + 8.dp,
|
||||
)
|
||||
.graphicsLayer {
|
||||
alpha = chatViewModel.chatAnimatedVisibility.value
|
||||
|
|
@ -154,7 +154,7 @@ private fun rememberAnimatedChatWidth(
|
|||
val maxChatWidth = 64.dp * 12
|
||||
val windowWidth = windowsState.size.width
|
||||
if (windowWidth != Dp.Unspecified) {
|
||||
val width = windowWidth - campaignScreenScope.leftOverlay.value.width - 16.dp
|
||||
val width = windowWidth - campaignScreenScope.leftPanel.value.width - 16.dp
|
||||
min(max(width, minChatWidth), maxChatWidth)
|
||||
} else {
|
||||
minChatWidth
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package com.pixelized.desktop.lwa.ui.screen.campaign.chat
|
||||
package com.pixelized.desktop.lwa.ui.screen.campaign.text
|
||||
|
||||
import androidx.compose.animation.core.Animatable
|
||||
import androidx.compose.animation.core.tween
|
||||
|
|
@ -6,7 +6,7 @@ import androidx.lifecycle.ViewModel
|
|||
import androidx.lifecycle.viewModelScope
|
||||
import com.pixelized.desktop.lwa.repository.network.NetworkRepository
|
||||
import com.pixelized.desktop.lwa.repository.settings.SettingsRepository
|
||||
import com.pixelized.desktop.lwa.ui.screen.campaign.chat.text.TextMessage
|
||||
import com.pixelized.desktop.lwa.ui.screen.campaign.text.messages.TextMessage
|
||||
import kotlinx.coroutines.flow.SharingStarted
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.mapNotNull
|
||||
|
|
@ -1,11 +1,11 @@
|
|||
package com.pixelized.desktop.lwa.ui.screen.campaign.chat
|
||||
package com.pixelized.desktop.lwa.ui.screen.campaign.text
|
||||
|
||||
import com.pixelized.desktop.lwa.repository.alteration.AlterationRepository
|
||||
import com.pixelized.desktop.lwa.repository.characterSheet.CharacterSheetRepository
|
||||
import com.pixelized.desktop.lwa.ui.screen.campaign.chat.text.CharacteristicTextMessageUio
|
||||
import com.pixelized.desktop.lwa.ui.screen.campaign.chat.text.DiminishedTextMessageUio
|
||||
import com.pixelized.desktop.lwa.ui.screen.campaign.chat.text.RollTextMessageUio
|
||||
import com.pixelized.desktop.lwa.ui.screen.campaign.chat.text.TextMessage
|
||||
import com.pixelized.desktop.lwa.ui.screen.campaign.text.messages.CharacteristicTextMessageUio
|
||||
import com.pixelized.desktop.lwa.ui.screen.campaign.text.messages.DiminishedTextMessageUio
|
||||
import com.pixelized.desktop.lwa.ui.screen.campaign.text.messages.RollTextMessageUio
|
||||
import com.pixelized.desktop.lwa.ui.screen.campaign.text.messages.TextMessage
|
||||
import com.pixelized.shared.lwa.model.AlteredCharacterSheetFactory
|
||||
import com.pixelized.shared.lwa.model.campaign.Campaign
|
||||
import com.pixelized.shared.lwa.model.campaign.CampaignJsonV1.CharacterInstanceJsonV1.CharacteristicV1.Damage
|
||||
|
|
@ -79,6 +79,7 @@ class TextMessageFactory(
|
|||
characterSheetId = payload.characterSheetId,
|
||||
) ?: return null
|
||||
val characterInstanceId = Campaign.CharacterInstance.Id(
|
||||
prefix = payload.prefix,
|
||||
characterSheetId = payload.characterSheetId,
|
||||
instanceId = payload.instanceId,
|
||||
)
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package com.pixelized.desktop.lwa.ui.screen.campaign.chat.text
|
||||
package com.pixelized.desktop.lwa.ui.screen.campaign.text.messages
|
||||
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Row
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package com.pixelized.desktop.lwa.ui.screen.campaign.chat.text
|
||||
package com.pixelized.desktop.lwa.ui.screen.campaign.text.messages
|
||||
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Row
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package com.pixelized.desktop.lwa.ui.screen.campaign.chat.text
|
||||
package com.pixelized.desktop.lwa.ui.screen.campaign.text.messages
|
||||
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Row
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package com.pixelized.desktop.lwa.ui.screen.campaign.chat.text
|
||||
package com.pixelized.desktop.lwa.ui.screen.campaign.text.messages
|
||||
|
||||
sealed interface TextMessage {
|
||||
val id : String
|
||||
|
|
@ -51,7 +51,7 @@ class CharacterSheetViewModel(
|
|||
val diminishedDialog: State<DiminishedStatDialogUio?> get() = _diminishedDialog
|
||||
|
||||
val diminishedValueFlow: StateFlow<Int?> = campaignRepository
|
||||
.characterInstanceFlow(id = argument.characterInstanceId)
|
||||
.characterInstanceFlow(characterInstanceId = argument.characterInstanceId)
|
||||
.map { instance -> instance.diminished.takeIf { it > 0 } }
|
||||
.stateIn(scope = viewModelScope, SharingStarted.Lazily, null)
|
||||
|
||||
|
|
@ -144,6 +144,7 @@ class CharacterSheetViewModel(
|
|||
val diminished = dialog.value().text.toIntOrNull() ?: 0
|
||||
network.share(
|
||||
payload = CampaignMessage.UpdateDiminished(
|
||||
prefix = dialog.characterInstanceId.prefix,
|
||||
characterSheetId = dialog.characterInstanceId.characterSheetId,
|
||||
instanceId = dialog.characterInstanceId.instanceId,
|
||||
diminished = diminished,
|
||||
|
|
|
|||
|
|
@ -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.ui.composable.error.ErrorSnackUio
|
||||
import com.pixelized.desktop.lwa.ui.navigation.screen.destination.CharacterSheetDestination
|
||||
import com.pixelized.desktop.lwa.ui.navigation.screen.destination.LevelUpDestination
|
||||
import com.pixelized.desktop.lwa.ui.overlay.roll.RollResult
|
||||
import com.pixelized.shared.lwa.model.characterSheet.CharacterSheet.CharacteristicId
|
||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||
|
|
@ -26,7 +26,7 @@ class LevelUpViewModel(
|
|||
private val levelUpFactory: LevelUpFactory,
|
||||
savedStateHandle: SavedStateHandle,
|
||||
) : ViewModel() {
|
||||
private val argument = CharacterSheetDestination.Argument(savedStateHandle)
|
||||
private val argument = LevelUpDestination.Argument(savedStateHandle)
|
||||
|
||||
private val _errors = MutableSharedFlow<ErrorSnackUio>()
|
||||
val error: SharedFlow<ErrorSnackUio> = _errors
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ import androidx.compose.ui.unit.dp
|
|||
|
||||
@Stable
|
||||
data class LwaShapes(
|
||||
val portrait: Shape,
|
||||
val panel: Shape,
|
||||
val settings: Shape,
|
||||
)
|
||||
|
|
@ -16,10 +17,12 @@ data class LwaShapes(
|
|||
@Stable
|
||||
@Composable
|
||||
fun lwaShapes(
|
||||
panel: Shape = RoundedCornerShape(16.dp),
|
||||
portrait: Shape = RoundedCornerShape(8.dp),
|
||||
panel: Shape = RoundedCornerShape(8.dp),
|
||||
settings: Shape = RoundedCornerShape(8.dp),
|
||||
): LwaShapes = remember {
|
||||
LwaShapes(
|
||||
portrait = portrait,
|
||||
panel = panel,
|
||||
settings = settings,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -30,15 +30,15 @@ class CharacterSheetService(
|
|||
return sheets.map { factory.convertToPreviewJson(sheet = it.value) }
|
||||
}
|
||||
|
||||
fun character(id: String): CharacterSheetJson? {
|
||||
fun characterSheet(id: String): CharacterSheetJson? {
|
||||
return sheets[id]?.let(factory::convertToJson)
|
||||
}
|
||||
|
||||
suspend fun updateCharacter(character: CharacterSheetJson) {
|
||||
suspend fun updateCharacterSheet(character: CharacterSheetJson) {
|
||||
return store.save(sheet = factory.convertFromJson(character))
|
||||
}
|
||||
|
||||
fun deleteCharacter(characterId: String): Boolean {
|
||||
fun deleteCharacterSheet(characterId: String): Boolean {
|
||||
return store.delete(id = characterId)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -27,6 +27,7 @@ class Engine(
|
|||
|
||||
is CampaignMessage -> {
|
||||
val instanceId = Campaign.CharacterInstance.Id(
|
||||
prefix = data.prefix,
|
||||
characterSheetId = data.characterSheetId,
|
||||
instanceId = data.instanceId,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ package com.pixelized.server.lwa.server
|
|||
import com.pixelized.server.lwa.server.rest.alteration.getActiveAlteration
|
||||
import com.pixelized.server.lwa.server.rest.alteration.getAlteration
|
||||
import com.pixelized.server.lwa.server.rest.alteration.putActiveAlteration
|
||||
import com.pixelized.server.lwa.server.rest.campaign.deleteCampaignCharacter
|
||||
import com.pixelized.server.lwa.server.rest.campaign.removeCampaignCharacter
|
||||
import com.pixelized.server.lwa.server.rest.campaign.deleteCampaignNpc
|
||||
import com.pixelized.server.lwa.server.rest.campaign.getCampaign
|
||||
import com.pixelized.server.lwa.server.rest.campaign.putCampaignCharacter
|
||||
|
|
@ -142,7 +142,7 @@ class LocalServer {
|
|||
)
|
||||
delete(
|
||||
path = "/delete",
|
||||
body = engine.deleteCampaignCharacter(),
|
||||
body = engine.removeCampaignCharacter(),
|
||||
)
|
||||
}
|
||||
route(path = "/npc") {
|
||||
|
|
|
|||
|
|
@ -1,26 +1,25 @@
|
|||
package com.pixelized.server.lwa.server.rest.alteration
|
||||
|
||||
import com.pixelized.server.lwa.server.Engine
|
||||
import com.pixelized.shared.lwa.model.campaign.Campaign
|
||||
import com.pixelized.server.lwa.utils.extentions.characterInstanceId
|
||||
import io.ktor.http.HttpStatusCode
|
||||
import io.ktor.server.response.respond
|
||||
import io.ktor.server.response.respondText
|
||||
|
||||
fun Engine.getActiveAlteration(): suspend io.ktor.server.routing.RoutingContext.() -> Unit {
|
||||
return {
|
||||
// get the query parameter
|
||||
val characterSheetId = call.queryParameters["characterSheetId"]
|
||||
val instanceId = call.queryParameters["instanceId"]?.toIntOrNull()
|
||||
// build the character instance id.
|
||||
val id = if (characterSheetId != null && instanceId != null) {
|
||||
Campaign.CharacterInstance.Id(
|
||||
characterSheetId = characterSheetId,
|
||||
instanceId = instanceId
|
||||
try {
|
||||
// get the query parameter
|
||||
val characterInstanceId = call.queryParameters.characterInstanceId
|
||||
// fetch the data from the service
|
||||
val data = alterationService.active(characterInstanceId = characterInstanceId)
|
||||
// respond to the client.
|
||||
call.respond(data)
|
||||
} catch (exception: Exception) {
|
||||
call.respondText(
|
||||
text = exception.localizedMessage,
|
||||
status = HttpStatusCode.UnprocessableEntity,
|
||||
)
|
||||
} else {
|
||||
null
|
||||
}
|
||||
// fetch the data from the service
|
||||
val data = id?.let { alterationService.active(it) } ?: emptyList()
|
||||
// respond to the client.
|
||||
call.respond(data)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
package com.pixelized.server.lwa.server.rest.alteration
|
||||
|
||||
import com.pixelized.server.lwa.server.Engine
|
||||
import com.pixelized.shared.lwa.model.campaign.Campaign
|
||||
import com.pixelized.server.lwa.utils.extentions.characterInstanceId
|
||||
import com.pixelized.shared.lwa.protocol.websocket.Message
|
||||
import com.pixelized.shared.lwa.protocol.websocket.payload.RestSynchronisation
|
||||
import io.ktor.http.HttpStatusCode
|
||||
|
|
@ -10,54 +10,45 @@ import io.ktor.server.response.respondText
|
|||
|
||||
fun Engine.putActiveAlteration(): suspend io.ktor.server.routing.RoutingContext.() -> Unit {
|
||||
return {
|
||||
// fetch the query parameters
|
||||
val characterSheetId = call.queryParameters["characterSheetId"]
|
||||
val instanceId = call.queryParameters["instanceId"]?.toIntOrNull()
|
||||
val alterationId = call.receive<String>()
|
||||
try {
|
||||
// get the query parameter
|
||||
val characterInstanceId = call.queryParameters.characterInstanceId
|
||||
|
||||
// build the characterInstanceId from the parameters
|
||||
val characterInstanceId = if (characterSheetId != null && instanceId != null) {
|
||||
Campaign.CharacterInstance.Id(
|
||||
characterSheetId = characterSheetId,
|
||||
instanceId = instanceId
|
||||
)
|
||||
} else {
|
||||
null
|
||||
}
|
||||
// fetch the query parameters
|
||||
val alterationId = call.receive<String>()
|
||||
|
||||
// Update the alteration
|
||||
val updated = characterInstanceId?.let {
|
||||
alterationService.toggleActiveAlteration(
|
||||
characterInstanceId = it,
|
||||
// Update the alteration
|
||||
val updated = alterationService.toggleActiveAlteration(
|
||||
characterInstanceId = characterInstanceId,
|
||||
alterationId = alterationId,
|
||||
)
|
||||
} ?: false
|
||||
|
||||
// build the Http response & send it
|
||||
val code = when (updated) {
|
||||
true -> HttpStatusCode.Accepted
|
||||
else -> HttpStatusCode.UnprocessableEntity
|
||||
}
|
||||
call.respondText(
|
||||
text = "$code",
|
||||
status = code,
|
||||
)
|
||||
|
||||
// share the modification to all client through the websocket.
|
||||
characterInstanceId?.let {
|
||||
if (!updated) {
|
||||
error("Unexpected error occurred when toggling the alteration (id:$alterationId) for the character (id:$characterInstanceId)")
|
||||
}
|
||||
// build the Http response & send it
|
||||
call.respondText(
|
||||
text = "$HttpStatusCode.Accepted",
|
||||
status = HttpStatusCode.Accepted,
|
||||
)
|
||||
// share the modification to all client through the websocket.
|
||||
webSocket.emit(
|
||||
Message(
|
||||
from = "Server",
|
||||
value = RestSynchronisation.ToggleActiveAlteration(
|
||||
characterId = campaignJsonFactory.convertToJson(id = it),
|
||||
characterId = campaignJsonFactory.convertToJson(id = characterInstanceId),
|
||||
alterationId = alterationId,
|
||||
active = alterationService.isAlterationActive(
|
||||
characterInstanceId = it,
|
||||
characterInstanceId = characterInstanceId,
|
||||
alterationId = alterationId
|
||||
),
|
||||
),
|
||||
)
|
||||
)
|
||||
} catch (exception: Exception) {
|
||||
call.respondText(
|
||||
text = exception.localizedMessage,
|
||||
status = HttpStatusCode.UnprocessableEntity,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,38 +1,39 @@
|
|||
package com.pixelized.server.lwa.server.rest.campaign
|
||||
|
||||
import com.pixelized.server.lwa.server.Engine
|
||||
import com.pixelized.shared.lwa.model.campaign.Campaign
|
||||
import com.pixelized.server.lwa.utils.extentions.characterInstanceId
|
||||
import com.pixelized.shared.lwa.protocol.websocket.Message
|
||||
import com.pixelized.shared.lwa.protocol.websocket.payload.RestSynchronisation
|
||||
import io.ktor.http.HttpStatusCode
|
||||
import io.ktor.server.response.respondText
|
||||
|
||||
fun Engine.deleteCampaignCharacter(): suspend io.ktor.server.routing.RoutingContext.() -> Unit {
|
||||
fun Engine.removeCampaignCharacter(): suspend io.ktor.server.routing.RoutingContext.() -> Unit {
|
||||
return {
|
||||
val characterSheetId = call.queryParameters["characterSheetId"]
|
||||
val instanceId = call.queryParameters["instanceId"]?.toIntOrNull()
|
||||
val id = if (characterSheetId != null && instanceId != null) {
|
||||
Campaign.CharacterInstance.Id(
|
||||
characterSheetId = characterSheetId,
|
||||
instanceId = instanceId
|
||||
try {
|
||||
// get the query parameter
|
||||
val characterInstanceId = call.queryParameters.characterInstanceId
|
||||
// remove the character form the party
|
||||
val updated = campaignService.removeCharacter(characterInstanceId = characterInstanceId)
|
||||
// error case
|
||||
if (!updated) {
|
||||
error("Unexpected error when removing character (id:$characterInstanceId) from party.")
|
||||
}
|
||||
// API & WebSocket responses
|
||||
call.respondText(
|
||||
text = "$HttpStatusCode.Accepted",
|
||||
status = HttpStatusCode.Accepted,
|
||||
)
|
||||
} else {
|
||||
null
|
||||
}
|
||||
val updated = id?.let { campaignService.removeCharacter(it) } ?: false
|
||||
val code = when (updated) {
|
||||
true -> HttpStatusCode.Accepted
|
||||
else -> HttpStatusCode.UnprocessableEntity
|
||||
}
|
||||
call.respondText(
|
||||
text = "$code",
|
||||
status = code,
|
||||
)
|
||||
webSocket.emit(
|
||||
Message(
|
||||
from = "Server",
|
||||
value = RestSynchronisation.Campaign,
|
||||
webSocket.emit(
|
||||
Message(
|
||||
from = "Server",
|
||||
value = RestSynchronisation.Campaign,
|
||||
)
|
||||
)
|
||||
)
|
||||
} catch (exception: Exception) {
|
||||
call.respondText(
|
||||
text = exception.localizedMessage,
|
||||
status = HttpStatusCode.UnprocessableEntity,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
package com.pixelized.server.lwa.server.rest.campaign
|
||||
|
||||
import com.pixelized.server.lwa.server.Engine
|
||||
import com.pixelized.shared.lwa.model.campaign.Campaign
|
||||
import com.pixelized.server.lwa.utils.extentions.characterInstanceId
|
||||
import com.pixelized.shared.lwa.protocol.websocket.Message
|
||||
import com.pixelized.shared.lwa.protocol.websocket.payload.RestSynchronisation
|
||||
import io.ktor.http.HttpStatusCode
|
||||
|
|
@ -9,30 +9,31 @@ import io.ktor.server.response.respondText
|
|||
|
||||
fun Engine.deleteCampaignNpc(): suspend io.ktor.server.routing.RoutingContext.() -> Unit {
|
||||
return {
|
||||
val characterSheetId = call.queryParameters["characterSheetId"]
|
||||
val instanceId = call.queryParameters["instanceId"]?.toIntOrNull()
|
||||
val id = if (characterSheetId != null && instanceId != null) {
|
||||
Campaign.CharacterInstance.Id(
|
||||
characterSheetId = characterSheetId,
|
||||
instanceId = instanceId
|
||||
try {
|
||||
// get the query parameter
|
||||
val characterInstanceId = call.queryParameters.characterInstanceId
|
||||
// remove the character form the party
|
||||
val updated = campaignService.removeNpc(npcInstanceId = characterInstanceId)
|
||||
// error case
|
||||
if (!updated) {
|
||||
error("Unexpected error when removing character (id:$characterInstanceId) from npcs.")
|
||||
}
|
||||
// API & WebSocket responses
|
||||
call.respondText(
|
||||
text = "$HttpStatusCode.Accepted",
|
||||
status = HttpStatusCode.Accepted,
|
||||
)
|
||||
} else {
|
||||
null
|
||||
}
|
||||
val updated = id?.let { campaignService.removeNpc(it) } ?: false
|
||||
val code = when (updated) {
|
||||
true -> HttpStatusCode.Accepted
|
||||
else -> HttpStatusCode.UnprocessableEntity
|
||||
}
|
||||
call.respondText(
|
||||
text = "$code",
|
||||
status = code,
|
||||
)
|
||||
webSocket.emit(
|
||||
Message(
|
||||
from = "Server",
|
||||
value = RestSynchronisation.Campaign,
|
||||
webSocket.emit(
|
||||
Message(
|
||||
from = "Server",
|
||||
value = RestSynchronisation.Campaign,
|
||||
)
|
||||
)
|
||||
)
|
||||
} catch (exception: Exception) {
|
||||
call.respondText(
|
||||
text = exception.localizedMessage,
|
||||
status = HttpStatusCode.UnprocessableEntity,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,6 +1,7 @@
|
|||
package com.pixelized.server.lwa.server.rest.campaign
|
||||
|
||||
import com.pixelized.server.lwa.server.Engine
|
||||
import com.pixelized.server.lwa.utils.extentions.characterSheetId
|
||||
import com.pixelized.shared.lwa.model.campaign.Campaign
|
||||
import com.pixelized.shared.lwa.protocol.websocket.Message
|
||||
import com.pixelized.shared.lwa.protocol.websocket.payload.RestSynchronisation
|
||||
|
|
@ -10,25 +11,26 @@ import io.ktor.server.response.respondText
|
|||
fun Engine.putCampaignCharacter(): suspend io.ktor.server.routing.RoutingContext.() -> Unit {
|
||||
return {
|
||||
try {
|
||||
val characterSheetId = call.queryParameters["characterSheetId"]
|
||||
?: error("missing character sheet id")
|
||||
|
||||
// get the query parameter
|
||||
val characterSheetId = call.queryParameters.characterSheetId
|
||||
// check if the character is already in the party.
|
||||
val instanceId = campaignService.campaign().characters.keys
|
||||
.firstOrNull { key -> key.characterSheetId == characterSheetId }
|
||||
|
||||
// handle the error case.
|
||||
if (instanceId != null) {
|
||||
error("Character Already in party")
|
||||
error("Character (characterSheetId:$characterSheetId) Already in party")
|
||||
}
|
||||
|
||||
// create the instance id for the character.
|
||||
val id = Campaign.CharacterInstance.Id(
|
||||
prefix = Campaign.CharacterInstance.Id.PLAYER,
|
||||
characterSheetId = characterSheetId,
|
||||
instanceId = 0,
|
||||
)
|
||||
|
||||
// add the character to the party.
|
||||
if (campaignService.addCharacter(id).not()) {
|
||||
error("Unexpected error occurred when the character instance was added to the party")
|
||||
}
|
||||
|
||||
// API & WebSocket responses.
|
||||
call.respondText(
|
||||
text = "Character $characterSheetId successfully added to the party",
|
||||
status = HttpStatusCode.Accepted,
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
package com.pixelized.server.lwa.server.rest.campaign
|
||||
|
||||
import com.pixelized.server.lwa.server.Engine
|
||||
import com.pixelized.server.lwa.utils.extentions.characterSheetId
|
||||
import com.pixelized.shared.lwa.model.campaign.Campaign
|
||||
import com.pixelized.shared.lwa.protocol.websocket.Message
|
||||
import com.pixelized.shared.lwa.protocol.websocket.payload.RestSynchronisation
|
||||
|
|
@ -10,9 +11,9 @@ import io.ktor.server.response.respondText
|
|||
fun Engine.putCampaignNpc(): suspend io.ktor.server.routing.RoutingContext.() -> Unit {
|
||||
return {
|
||||
try {
|
||||
val characterSheetId = call.queryParameters["characterSheetId"]
|
||||
?: error("missing character sheet id")
|
||||
|
||||
// get the query parameter
|
||||
val characterSheetId = call.queryParameters.characterSheetId
|
||||
// compute the npc id base on similar character sheets.
|
||||
val instanceId = campaignService.campaign().npcs.keys
|
||||
.filter { it.characterSheetId == characterSheetId }
|
||||
.reduceOrNull { acc, id ->
|
||||
|
|
@ -22,16 +23,17 @@ fun Engine.putCampaignNpc(): suspend io.ktor.server.routing.RoutingContext.() ->
|
|||
acc
|
||||
}
|
||||
}
|
||||
|
||||
// create the instance id for the character.
|
||||
val id = Campaign.CharacterInstance.Id(
|
||||
prefix = Campaign.CharacterInstance.Id.NPC,
|
||||
characterSheetId = characterSheetId,
|
||||
instanceId = instanceId?.let { it.instanceId + 1 } ?: 0,
|
||||
)
|
||||
|
||||
// add the character to the npcs.
|
||||
if (campaignService.addNpc(id).not()) {
|
||||
error("Unexpected error occurred when the character instance was added to the npcs")
|
||||
}
|
||||
|
||||
// API & WebSocket responses.
|
||||
call.respondText(
|
||||
text = "Character $characterSheetId successfully added to the npcs",
|
||||
status = HttpStatusCode.Accepted,
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
package com.pixelized.server.lwa.server.rest.character
|
||||
|
||||
import com.pixelized.server.lwa.server.Engine
|
||||
import com.pixelized.server.lwa.utils.extentions.characterSheetId
|
||||
import com.pixelized.shared.lwa.protocol.websocket.Message
|
||||
import com.pixelized.shared.lwa.protocol.websocket.payload.RestSynchronisation
|
||||
import io.ktor.http.HttpStatusCode
|
||||
|
|
@ -8,10 +9,10 @@ import io.ktor.server.response.respondText
|
|||
|
||||
fun Engine.deleteCharacter(): suspend io.ktor.server.routing.RoutingContext.() -> Unit {
|
||||
return {
|
||||
val id = call.parameters["id"]
|
||||
val deleted = id?.let(characterService::deleteCharacter) ?: false
|
||||
val characterSheetId = call.parameters.characterSheetId
|
||||
val deleted = characterService.deleteCharacterSheet(characterId = characterSheetId)
|
||||
|
||||
if (deleted && id != null) {
|
||||
if (deleted) {
|
||||
call.respondText(
|
||||
text = "${HttpStatusCode.OK}",
|
||||
status = HttpStatusCode.OK,
|
||||
|
|
@ -19,7 +20,7 @@ fun Engine.deleteCharacter(): suspend io.ktor.server.routing.RoutingContext.() -
|
|||
webSocket.emit(
|
||||
Message(
|
||||
from = "Server",
|
||||
value = RestSynchronisation.CharacterDelete(characterId = id),
|
||||
value = RestSynchronisation.CharacterDelete(characterId = characterSheetId),
|
||||
)
|
||||
)
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -1,15 +1,16 @@
|
|||
package com.pixelized.server.lwa.server.rest.character
|
||||
|
||||
import com.pixelized.server.lwa.server.Engine
|
||||
import com.pixelized.shared.lwa.model.characterSheet.CharacterSheetJson
|
||||
import com.pixelized.server.lwa.utils.extentions.characterSheetId
|
||||
import io.ktor.http.HttpStatusCode
|
||||
import io.ktor.server.response.respond
|
||||
import io.ktor.server.response.respondText
|
||||
|
||||
fun Engine.getCharacter(): suspend io.ktor.server.routing.RoutingContext.() -> Unit {
|
||||
return {
|
||||
val id = call.queryParameters["id"]
|
||||
val body: CharacterSheetJson? = id?.let(characterService::character)
|
||||
val id = call.queryParameters.characterSheetId
|
||||
val body = characterService.characterSheet(id)
|
||||
|
||||
if (body != null) {
|
||||
call.respond(body)
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ import io.ktor.server.response.respondText
|
|||
fun Engine.putCharacter(): suspend io.ktor.server.routing.RoutingContext.() -> Unit {
|
||||
return {
|
||||
val form = call.receive<CharacterSheetJson>()
|
||||
characterService.updateCharacter(
|
||||
characterService.updateCharacterSheet(
|
||||
character = form
|
||||
)
|
||||
call.respondText(
|
||||
|
|
|
|||
|
|
@ -0,0 +1,20 @@
|
|||
package com.pixelized.server.lwa.utils.extentions
|
||||
|
||||
import com.pixelized.shared.lwa.model.campaign.Campaign
|
||||
import io.ktor.http.Parameters
|
||||
|
||||
val Parameters.characterInstanceId: Campaign.CharacterInstance.Id
|
||||
get() = Campaign.CharacterInstance.Id(
|
||||
characterSheetId = characterSheetId,
|
||||
instanceId = instanceId,
|
||||
prefix = prefix,
|
||||
)
|
||||
|
||||
val Parameters.characterSheetId
|
||||
get() = this["characterSheetId"] ?: error("Missing character sheet id.")
|
||||
|
||||
val Parameters.instanceId: Int
|
||||
get() = this["instanceId"]?.toIntOrNull() ?: error("Missing character instance id.")
|
||||
|
||||
val Parameters.prefix: Char
|
||||
get() = this["prefix"]?.get(0) ?: error("Missing character prefix.")
|
||||
|
|
@ -10,9 +10,25 @@ data class Campaign(
|
|||
val diminished: Int,
|
||||
) {
|
||||
data class Id(
|
||||
val prefix: Char,
|
||||
val characterSheetId: String,
|
||||
val instanceId: Int,
|
||||
)
|
||||
) {
|
||||
fun equals(
|
||||
prefix: Char,
|
||||
characterSheetId: String?,
|
||||
instanceId: Int?,
|
||||
): Boolean {
|
||||
return this.prefix == prefix &&
|
||||
this.characterSheetId == characterSheetId &&
|
||||
this.instanceId == instanceId
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val PLAYER = 'c'
|
||||
const val NPC = 'n'
|
||||
}
|
||||
}
|
||||
|
||||
enum class Characteristic {
|
||||
Damage,
|
||||
|
|
|
|||
|
|
@ -65,7 +65,7 @@ class CampaignJsonFactory(
|
|||
fun convertToJson(
|
||||
id: Campaign.CharacterInstance.Id,
|
||||
): String {
|
||||
return "${String.format("%03d", id.instanceId)}-${id.characterSheetId}"
|
||||
return "${id.prefix}-${String.format("%03d", id.instanceId)}-${id.characterSheetId}"
|
||||
}
|
||||
|
||||
fun convertToJson(
|
||||
|
|
|
|||
|
|
@ -33,8 +33,9 @@ class CampaignJsonV1Factory {
|
|||
characterInstanceIdJson: String,
|
||||
): Campaign.CharacterInstance.Id {
|
||||
return Campaign.CharacterInstance.Id(
|
||||
characterSheetId = characterInstanceIdJson.drop(4), // drop first 3 number then the -
|
||||
instanceId = characterInstanceIdJson.take(3).toIntOrNull() ?: 0,
|
||||
prefix = characterInstanceIdJson.take(1)[0],
|
||||
characterSheetId = characterInstanceIdJson.drop(2 + 4), // drop the char then the - then the first 3 number then the -
|
||||
instanceId = characterInstanceIdJson.drop(2).take(3).toIntOrNull() ?: 0,
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -5,11 +5,13 @@ import kotlinx.serialization.Serializable
|
|||
|
||||
@Serializable
|
||||
sealed interface CampaignMessage : MessagePayload {
|
||||
val prefix: Char
|
||||
val characterSheetId: String
|
||||
val instanceId: Int
|
||||
|
||||
@Serializable
|
||||
data class UpdateCharacteristic(
|
||||
override val prefix: Char,
|
||||
override val characterSheetId: String,
|
||||
override val instanceId: Int,
|
||||
val characteristic: CampaignJsonV1.CharacterInstanceJsonV1.CharacteristicV1,
|
||||
|
|
@ -18,6 +20,7 @@ sealed interface CampaignMessage : MessagePayload {
|
|||
|
||||
@Serializable
|
||||
data class UpdateDiminished(
|
||||
override val prefix: Char,
|
||||
override val characterSheetId: String,
|
||||
override val instanceId: Int,
|
||||
val diminished: Int,
|
||||
|
|
|
|||
|
|
@ -6,7 +6,9 @@ import java.util.UUID
|
|||
@Serializable
|
||||
data class RollMessage(
|
||||
val id: RollId,
|
||||
val prefix: Char,
|
||||
val characterSheetId: String,
|
||||
val instanceId: Int?,
|
||||
val skillLabel: String,
|
||||
val rollValue: Int,
|
||||
val resultLabel: String? = null,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue