Add auto check feature to skill after a successfull roll.

This commit is contained in:
Andres Gomez, Thomas (ITDV RL) 2025-04-12 11:48:55 +02:00
parent 9588a3bfaf
commit 04b203239d
9 changed files with 116 additions and 35 deletions

View file

@ -31,7 +31,7 @@ class PortraitOverlayViewModel(
.stateIn( .stateIn(
scope = viewModelScope, scope = viewModelScope,
started = SharingStarted.Eagerly, started = SharingStarted.Eagerly,
initialValue = null initialValue = defaultPortrait()
) )
val options = settingsRepository.settingsFlow() val options = settingsRepository.settingsFlow()
@ -45,10 +45,7 @@ class PortraitOverlayViewModel(
.stateIn( .stateIn(
scope = viewModelScope, scope = viewModelScope,
started = SharingStarted.Eagerly, started = SharingStarted.Eagerly,
initialValue = PortraitOptionUio( initialValue = defaultOptions()
isGameMaster = false,
isBrowserAvailable = false,
)
) )
suspend fun dismissPortrait() { suspend fun dismissPortrait() {
@ -68,4 +65,11 @@ class PortraitOverlayViewModel(
} }
} }
} }
private fun defaultPortrait() = null
private fun defaultOptions() = PortraitOptionUio(
isGameMaster = false,
isBrowserAvailable = false,
)
} }

View file

@ -49,8 +49,15 @@ class RollHostState {
} }
@Stable @Stable
enum class RollResult { interface RollResult {
Dismissed,
@Stable
data class BoundedRollResult(
val result: Result,
val difficulty: Difficulty,
) : RollResult {
@Stable
enum class Result {
CriticalSuccess, CriticalSuccess,
SpecialSuccess, SpecialSuccess,
Success, Success,
@ -58,6 +65,23 @@ enum class RollResult {
CriticalFailure, CriticalFailure,
} }
@Stable
enum class Difficulty{
Easy,
Normal,
Hard,
Impossible
}
}
@Stable
data class BoundlessRollResult(
val value: Int,
) : RollResult
data object Dismissed: RollResult
}
@Stable @Stable
interface RollAction { interface RollAction {
val roll: Uio val roll: Uio

View file

@ -83,6 +83,7 @@ data class DifficultyUio(
val open: Boolean, val open: Boolean,
val difficulty: Difficulty, val difficulty: Difficulty,
) { ) {
@Stable
enum class Difficulty { enum class Difficulty {
EASY, NORMAL, HARD, IMPOSSIBLE EASY, NORMAL, HARD, IMPOSSIBLE
} }

View file

@ -212,16 +212,29 @@ class RollViewModel(
} }
} }
lastRollResult = rollStep?.let { // build the roll result.
when (roll) { lastRollResult = if (rollStep != null) {
in it.criticalSuccess -> RollResult.CriticalSuccess RollResult.BoundedRollResult(
in it.specialSuccess -> RollResult.SpecialSuccess result = when (roll) {
in it.success -> RollResult.Success in rollStep.criticalSuccess -> RollResult.BoundedRollResult.Result.CriticalSuccess
in it.failure -> RollResult.Failure in rollStep.specialSuccess -> RollResult.BoundedRollResult.Result.SpecialSuccess
in it.criticalFailure -> RollResult.CriticalFailure in rollStep.success -> RollResult.BoundedRollResult.Result.Success
else -> RollResult.Dismissed in rollStep.criticalFailure -> RollResult.BoundedRollResult.Result.CriticalFailure
else -> RollResult.BoundedRollResult.Result.Failure
},
difficulty = when (_rollDifficulty.value?.difficulty) {
Difficulty.IMPOSSIBLE -> RollResult.BoundedRollResult.Difficulty.Impossible
Difficulty.HARD -> RollResult.BoundedRollResult.Difficulty.Hard
Difficulty.NORMAL -> RollResult.BoundedRollResult.Difficulty.Normal
else -> RollResult.BoundedRollResult.Difficulty.Easy
}
)
} else {
RollResult.BoundlessRollResult(
value = roll,
)
} }
} ?: RollResult.Dismissed
_rollResult.value = RollResultUio( _rollResult.value = RollResultUio(
label = resultLabel ?: "", label = resultLabel ?: "",

View file

@ -37,6 +37,7 @@ import com.pixelized.desktop.lwa.ui.composable.character.characteristic.Characte
import com.pixelized.desktop.lwa.ui.composable.character.characteristic.CharacterSheetCharacteristicDialogViewModel import com.pixelized.desktop.lwa.ui.composable.character.characteristic.CharacterSheetCharacteristicDialogViewModel
import com.pixelized.desktop.lwa.ui.composable.character.diminished.CharacterSheetDiminishedDialogViewModel import com.pixelized.desktop.lwa.ui.composable.character.diminished.CharacterSheetDiminishedDialogViewModel
import com.pixelized.desktop.lwa.ui.overlay.roll.RollAction import com.pixelized.desktop.lwa.ui.overlay.roll.RollAction
import com.pixelized.desktop.lwa.ui.overlay.roll.RollResult
import com.pixelized.desktop.lwa.ui.screen.campaign.player.detail.header.CharacterDetailHeader import com.pixelized.desktop.lwa.ui.screen.campaign.player.detail.header.CharacterDetailHeader
import com.pixelized.desktop.lwa.ui.screen.campaign.player.detail.header.CharacterDetailHeaderUio import com.pixelized.desktop.lwa.ui.screen.campaign.player.detail.header.CharacterDetailHeaderUio
import com.pixelized.desktop.lwa.ui.screen.campaign.player.detail.sheet.CharacterDetailSheet import com.pixelized.desktop.lwa.ui.screen.campaign.player.detail.sheet.CharacterDetailSheet
@ -123,6 +124,10 @@ fun CharacterDetailPanel(
onSkill = { onSkill = {
scope.launch { scope.launch {
val result = roll.showRollOverlay(roll = it.roll) val result = roll.showRollOverlay(roll = it.roll)
detailViewModel.onSkillUse(
skillId = it.skillId,
result = result,
)
println("result: $result") println("result: $result")
} }
}, },

View file

@ -8,6 +8,9 @@ import com.pixelized.desktop.lwa.repository.network.NetworkRepository
import com.pixelized.desktop.lwa.repository.settings.SettingsRepository import com.pixelized.desktop.lwa.repository.settings.SettingsRepository
import com.pixelized.desktop.lwa.ui.navigation.window.WindowController import com.pixelized.desktop.lwa.ui.navigation.window.WindowController
import com.pixelized.desktop.lwa.ui.navigation.window.destination.navigateToCharacterSheetEdit import com.pixelized.desktop.lwa.ui.navigation.window.destination.navigateToCharacterSheetEdit
import com.pixelized.desktop.lwa.ui.overlay.roll.RollResult
import com.pixelized.desktop.lwa.ui.overlay.roll.RollResult.BoundedRollResult.Difficulty
import com.pixelized.desktop.lwa.ui.overlay.roll.RollResult.BoundedRollResult.Result
import com.pixelized.shared.lwa.protocol.websocket.CharacterSheetEvent import com.pixelized.shared.lwa.protocol.websocket.CharacterSheetEvent
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.SharingStarted
@ -15,7 +18,6 @@ import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.launch
import lwacharactersheet.composeapp.generated.resources.Res import lwacharactersheet.composeapp.generated.resources.Res
import lwacharactersheet.composeapp.generated.resources.character_sheet_edit__edit__title import lwacharactersheet.composeapp.generated.resources.character_sheet_edit__edit__title
import org.jetbrains.compose.resources.getString import org.jetbrains.compose.resources.getString
@ -40,7 +42,7 @@ class CharacterDetailViewModel(
characterSheetRepository.characterDetailFlow(characterSheetId = characterSheetId), characterSheetRepository.characterDetailFlow(characterSheetId = characterSheetId),
alterationRepository.fieldAlterationsFlow(characterSheetId = characterSheetId), alterationRepository.fieldAlterationsFlow(characterSheetId = characterSheetId),
settingRepository.settingsFlow() settingRepository.settingsFlow()
) { characterSheet, alterations, settings, -> ) { characterSheet, alterations, settings ->
characterDetailFactory.convertToCharacterDetailHeaderUio( characterDetailFactory.convertToCharacterDetailHeaderUio(
characterSheetId = characterSheetId, characterSheetId = characterSheetId,
characterSheet = characterSheet, characterSheet = characterSheet,
@ -100,12 +102,41 @@ class CharacterDetailViewModel(
displayedCharacterId.value = null displayedCharacterId.value = null
} }
suspend fun onSkillUse(
skillId: String,
result: RollResult,
) {
val characterSheetId = displayedCharacterId.value ?: return
// check if the RollResult is a BoundedRollResult. can work with other roll result.
val roll = result as? RollResult.BoundedRollResult ?: return
// check if the roll is a success with some challenge.
val difficulty = roll.difficulty != Difficulty.Easy
val success = roll.result.let {
it == Result.Success || it == Result.SpecialSuccess || it == Result.CriticalSuccess
}
if (!success || !difficulty) return
// Get the skill that have been rolled.
val skill = characterSheetRepository
.characterDetail(characterSheetId = characterSheetId)
?.skill(id = skillId)
// Check the skill if needed.
if (skill?.used == false) {
network.share(
message = CharacterSheetEvent.UpdateSkillUsageEvent(
timestamp = System.currentTimeMillis(),
characterSheetId = characterSheetId,
skillId = skillId,
used = true,
)
)
}
}
suspend fun onSkillUse( suspend fun onSkillUse(
skillId: String, skillId: String,
used: Boolean, used: Boolean,
) { ) {
val characterSheetId = displayedCharacterId.value ?: return val characterSheetId = displayedCharacterId.value ?: return
network.share( network.share(
message = CharacterSheetEvent.UpdateSkillUsageEvent( message = CharacterSheetEvent.UpdateSkillUsageEvent(
timestamp = System.currentTimeMillis(), timestamp = System.currentTimeMillis(),

View file

@ -2,8 +2,8 @@ package com.pixelized.desktop.lwa.ui.screen.levelup
import com.pixelized.desktop.lwa.ui.composable.tooltip.TooltipUio import com.pixelized.desktop.lwa.ui.composable.tooltip.TooltipUio
import com.pixelized.desktop.lwa.ui.overlay.roll.RollAction import com.pixelized.desktop.lwa.ui.overlay.roll.RollAction
import com.pixelized.desktop.lwa.ui.overlay.roll.RollResult import com.pixelized.desktop.lwa.ui.overlay.roll.RollResult
import com.pixelized.desktop.lwa.ui.overlay.roll.RollResult.BoundedRollResult.Result
import com.pixelized.desktop.lwa.ui.screen.levelup.items.LevelUpCharacteristicUio import com.pixelized.desktop.lwa.ui.screen.levelup.items.LevelUpCharacteristicUio
import com.pixelized.desktop.lwa.ui.screen.levelup.items.LevelUpSkillUio import com.pixelized.desktop.lwa.ui.screen.levelup.items.LevelUpSkillUio
import com.pixelized.desktop.lwa.ui.screen.levelup.items.LevelUpSubCharacteristicUio import com.pixelized.desktop.lwa.ui.screen.levelup.items.LevelUpSubCharacteristicUio
@ -11,7 +11,6 @@ import com.pixelized.shared.lwa.model.AlteredCharacterSheet
import com.pixelized.shared.lwa.model.AlteredCharacterSheetFactory import com.pixelized.shared.lwa.model.AlteredCharacterSheetFactory
import com.pixelized.shared.lwa.model.alteration.Alteration import com.pixelized.shared.lwa.model.alteration.Alteration
import com.pixelized.shared.lwa.model.alteration.FieldAlteration import com.pixelized.shared.lwa.model.alteration.FieldAlteration
import com.pixelized.shared.lwa.model.campaign.Campaign
import com.pixelized.shared.lwa.model.characterSheet.CharacterSheet import com.pixelized.shared.lwa.model.characterSheet.CharacterSheet
import com.pixelized.shared.lwa.parser.expression.Expression import com.pixelized.shared.lwa.parser.expression.Expression
import com.pixelized.shared.lwa.usecase.ExpressionUseCase import com.pixelized.shared.lwa.usecase.ExpressionUseCase
@ -233,7 +232,7 @@ class LevelUpFactory(
fun convertToLevelUpSectionUio( fun convertToLevelUpSectionUio(
characterSheetId: String, characterSheetId: String,
characterSheet: CharacterSheet?, characterSheet: CharacterSheet?,
results: Map<String, RollResult>, results: Map<String, RollResult.BoundedRollResult>,
): LevelUpSectionUio? { ): LevelUpSectionUio? {
if (characterSheet == null) return null if (characterSheet == null) return null
@ -286,7 +285,7 @@ class LevelUpFactory(
characterSheetId: String, characterSheetId: String,
alteredCharacterSheet: AlteredCharacterSheet, alteredCharacterSheet: AlteredCharacterSheet,
skill: CharacterSheet.Skill, skill: CharacterSheet.Skill,
results: Map<String, RollResult>, results: Map<String, RollResult.BoundedRollResult>,
): LevelUpSkillUio? { ): LevelUpSkillUio? {
if (!skill.used) return null if (!skill.used) return null
@ -326,8 +325,8 @@ class LevelUpFactory(
} }
} }
fun Map<String, RollResult>.isSkillLeveledUp(skillId: String): Boolean { fun Map<String, RollResult.BoundedRollResult>.isSkillLeveledUp(skillId: String): Boolean {
return this.getOrElse(skillId) { RollResult.Dismissed }.let { return this[skillId]?.let { roll ->
it == RollResult.CriticalSuccess || it == RollResult.SpecialSuccess || it == RollResult.Success roll.result == Result.CriticalSuccess || roll.result == Result.SpecialSuccess || roll.result == Result.Success
} } ?: false
} }

View file

@ -31,7 +31,7 @@ class LevelUpViewModel(
private val _errors = MutableSharedFlow<ErrorSnackUio>() private val _errors = MutableSharedFlow<ErrorSnackUio>()
val error: SharedFlow<ErrorSnackUio> = _errors val error: SharedFlow<ErrorSnackUio> = _errors
private val results = MutableStateFlow<Map<String, RollResult>>(emptyMap()) private val results = MutableStateFlow<Map<String, RollResult.BoundedRollResult>>(emptyMap())
private val selectedCharacteristicId = MutableStateFlow<String?>(null) private val selectedCharacteristicId = MutableStateFlow<String?>(null)
val header = combine( val header = combine(
@ -89,7 +89,7 @@ class LevelUpViewModel(
fun applyRollResult(skillId: String, result: RollResult) { fun applyRollResult(skillId: String, result: RollResult) {
// Discard this case. // Discard this case.
if (result == RollResult.Dismissed) return if (result !is RollResult.BoundedRollResult) return
// If not save the roll result to the map. // If not save the roll result to the map.
results.value = results.value.toMutableMap().also { results.value = results.value.toMutableMap().also {
it[skillId] = result it[skillId] = result

View file

@ -27,6 +27,10 @@ data class CharacterSheet(
// actions // actions
val actions: List<Roll>, val actions: List<Roll>,
) { ) {
fun skill(id: String?): Skill? = commonSkills.firstOrNull { it.id == id }
?: specialSkills.firstOrNull { it.id == id }
?: magicSkills.firstOrNull { it.id == id }
data class Skill( data class Skill(
val id: String, val id: String,
val label: String, val label: String,