Link the player character ribbon to the server.

This commit is contained in:
Thomas Andres Gomez 2025-02-24 21:35:33 +01:00
parent ed1b27039d
commit 4ed11660c3
17 changed files with 228 additions and 72 deletions

View file

@ -20,6 +20,7 @@ import com.pixelized.desktop.lwa.repository.roll_history.RollHistoryRepository
import com.pixelized.desktop.lwa.repository.settings.SettingsFactory import com.pixelized.desktop.lwa.repository.settings.SettingsFactory
import com.pixelized.desktop.lwa.repository.settings.SettingsRepository import com.pixelized.desktop.lwa.repository.settings.SettingsRepository
import com.pixelized.desktop.lwa.repository.settings.SettingsStore import com.pixelized.desktop.lwa.repository.settings.SettingsStore
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.CharacterDetailViewModel
import com.pixelized.desktop.lwa.ui.screen.campaign.player.detail.CharacterDiminishedViewModel 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.PlayerRibbonFactory
@ -105,6 +106,7 @@ val factoryDependencies
factoryOf(::SettingsFactory) factoryOf(::SettingsFactory)
factoryOf(::CampaignJsonFactory) factoryOf(::CampaignJsonFactory)
factoryOf(::PlayerRibbonFactory) factoryOf(::PlayerRibbonFactory)
factoryOf(::CharacterDetailFactory)
} }
val viewModelDependencies val viewModelDependencies
@ -134,4 +136,4 @@ val useCaseDependencies
factoryOf(::ExpressionUseCase) factoryOf(::ExpressionUseCase)
factoryOf(::SettingsUseCase) factoryOf(::SettingsUseCase)
factoryOf(::CharacterSheetUseCase) factoryOf(::CharacterSheetUseCase)
} }

View file

@ -99,6 +99,10 @@ class ExpressionUseCase(
rollUseCase.roll(expression.dice) rollUseCase.roll(expression.dice)
} }
is Expression.UrlExpression -> {
0 // Ignore this case.
}
is Expression.WordExpression -> when (expression.word.type) { is Expression.WordExpression -> when (expression.word.type) {
Word.Type.BDC -> evaluate(expressionParser.parse(sheet.damageBonus)) Word.Type.BDC -> evaluate(expressionParser.parse(sheet.damageBonus))
Word.Type.BDD -> evaluate(expressionParser.parse(sheet.damageBonus)) Word.Type.BDD -> evaluate(expressionParser.parse(sheet.damageBonus))

View file

@ -67,6 +67,14 @@ sealed interface Expression {
} }
} }
data class UrlExpression(
val url: String,
) : Expression {
override fun toString(): String {
return url
}
}
data class DiceExpression( data class DiceExpression(
val dice: Dice, val dice: Dice,
) : Expression { ) : Expression {

View file

@ -2,6 +2,9 @@ package com.pixelized.desktop.lwa.parser.expression
import com.pixelized.desktop.lwa.parser.dice.DiceParser import com.pixelized.desktop.lwa.parser.dice.DiceParser
import com.pixelized.desktop.lwa.parser.word.WordParser import com.pixelized.desktop.lwa.parser.word.WordParser
import org.jetbrains.skia.toIPoint
import java.net.URI
import java.net.URL
/** /**
* Highly inspired by the following javascript implementation: * Highly inspired by the following javascript implementation:
@ -15,6 +18,12 @@ class ExpressionParser(
private val tokenBreak = arrayOf( private val tokenBreak = arrayOf(
'+', '-', '/', '*', '(', ')', ',' '+', '-', '/', '*', '(', ')', ','
) )
private val tokenBreakException = mapOf(
'/' to listOf("http:", "https:"),
'.' to listOf("http:", "https:"),
'+' to listOf("http:", "https:"),
'-' to listOf("http:", "https:"),
)
private val operators = mapOf( private val operators = mapOf(
'+' to Operator( '+' to Operator(
evaluations = { first, second -> Expression.Add(first, second) }, evaluations = { first, second -> Expression.Add(first, second) },
@ -50,8 +59,11 @@ class ExpressionParser(
* Every characters that are not un the [Companion.tokenBreak] list can be part of a token. * Every characters that are not un the [Companion.tokenBreak] list can be part of a token.
* @see Companion.tokenBreak * @see Companion.tokenBreak
*/ */
private fun isToken(): Boolean = stack.peek().let { private fun isToken(
it != null && !tokenBreak.contains(it) currentToken: String? = null,
): Boolean = stack.peek().let {
it != null && !tokenBreak.contains(it) ||
(tokenBreakException[it]?.any { currentToken?.contains(it) ?: false } ?: false)
} }
/** /**
@ -69,7 +81,7 @@ class ExpressionParser(
val token = StringBuilder() val token = StringBuilder()
do { do {
stack.pull().let(token::append) stack.pull().let(token::append)
} while (isToken()) } while (isToken(token.toString()))
return token.toString() return token.toString()
} }
@ -186,6 +198,16 @@ class ExpressionParser(
return Expression.DiceExpression(dice) return Expression.DiceExpression(dice)
} }
val url = try {
println(token)
URI.create(token).toString()
} catch (_: Exception) {
null
}
if (url != null) {
return Expression.UrlExpression(url)
}
throw Error.UnRecognizedToken(actual = token, expression = stack.input) throw Error.UnRecognizedToken(actual = token, expression = stack.input)
} }
} }

View file

@ -15,7 +15,7 @@ class AlterationRepository(
private val activeAlterationIdMapFlow: HashMap<CharacterId, MutableStateFlow<List<AlterationId>>> = private val activeAlterationIdMapFlow: HashMap<CharacterId, MutableStateFlow<List<AlterationId>>> =
hashMapOf("0f2117e9-e077-4354-8d77-20150df1c462" to MutableStateFlow(listOf("7c00dafa-a67d-4351-8ea9-67d933012cde", "65e37d32-3031-4bf8-9369-d2c45d2efac0"))) hashMapOf("0f2117e9-e077-4354-8d77-20150df1c462" to MutableStateFlow(listOf("7c00dafa-a67d-4351-8ea9-67d933012cde", "65e37d32-3031-4bf8-9369-d2c45d2efac0")))
fun alterations(characterId: String): Flow<Map<String, List<FieldAlteration>>> { fun alterationsFlow(characterId: String): Flow<Map<String, List<FieldAlteration>>> {
return activeAlterationIdMapFlow return activeAlterationIdMapFlow
.getOrPut(characterId) { MutableStateFlow(emptyList()) } .getOrPut(characterId) { MutableStateFlow(emptyList()) }
.map { activeAlterationIds -> .map { activeAlterationIds ->

View file

@ -9,7 +9,6 @@ import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.launch
class CharacterSheetRepository( class CharacterSheetRepository(
private val store: CharacterSheetStore, private val store: CharacterSheetStore,
@ -17,6 +16,7 @@ class CharacterSheetRepository(
private val scope = CoroutineScope(Dispatchers.IO + Job()) private val scope = CoroutineScope(Dispatchers.IO + Job())
val characterSheetPreviewFlow get() = store.previewFlow val characterSheetPreviewFlow get() = store.previewFlow
val characterDetailFlow get() = store.detailFlow
fun characterPreview(characterId: String?): CharacterSheetPreview? { fun characterPreview(characterId: String?): CharacterSheetPreview? {
return characterSheetPreviewFlow.value.firstOrNull { it.id == characterId } return characterSheetPreviewFlow.value.firstOrNull { it.id == characterId }
@ -35,17 +35,8 @@ class CharacterSheetRepository(
fun characterDetailFlow( fun characterDetailFlow(
characterId: String?, characterId: String?,
forceUpdate: Boolean = false,
): StateFlow<CharacterSheet?> { ): StateFlow<CharacterSheet?> {
val initial = store.detailFlow.value[characterId] val initial = store.detailFlow.value[characterId]
if (forceUpdate || initial == null) {
scope.launch {
characterDetail(
characterId = characterId,
forceUpdate = forceUpdate,
)
}
}
return store.detailFlow return store.detailFlow
.map { sheets -> .map { sheets ->
sheets[characterId] sheets[characterId]

View file

@ -45,7 +45,7 @@ import org.jetbrains.compose.resources.painterResource
import org.koin.compose.viewmodel.koinViewModel import org.koin.compose.viewmodel.koinViewModel
@Stable @Stable
data class CharacterDetailUio( data class CharacterDetailHeaderUio(
val id: String, val id: String,
val portrait: String?, val portrait: String?,
val name: String, val name: String,
@ -55,7 +55,7 @@ data class CharacterDetailUio(
) )
@Stable @Stable
data class CharacterDynDetailUio( data class CharacterDetailHeaderInstanceUio(
val hp: String, val hp: String,
val pp: String, val pp: String,
) )
@ -109,8 +109,8 @@ fun CharacterDetail(
@Composable @Composable
fun CharacterDetailContent( fun CharacterDetailContent(
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
character: CharacterDetailUio, character: CharacterDetailHeaderUio,
dynDetail: State<CharacterDynDetailUio?>, dynDetail: State<CharacterDetailHeaderInstanceUio?>,
onDismissRequest: () -> Unit, onDismissRequest: () -> Unit,
onDiminished: () -> Unit, onDiminished: () -> Unit,
onHp: () -> Unit, onHp: () -> Unit,
@ -139,7 +139,7 @@ fun CharacterDetailContent(
@Composable @Composable
private fun Background( private fun Background(
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
character: CharacterDetailUio, character: CharacterDetailHeaderUio,
) { ) {
Surface( Surface(
modifier = modifier.fillMaxSize(), modifier = modifier.fillMaxSize(),
@ -168,8 +168,8 @@ private fun Background(
@Composable @Composable
private fun CharacterHeader( private fun CharacterHeader(
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
character: CharacterDetailUio, character: CharacterDetailHeaderUio,
dynDetail: State<CharacterDynDetailUio?>, dynDetail: State<CharacterDetailHeaderInstanceUio?>,
onDismissRequest: () -> Unit, onDismissRequest: () -> Unit,
onDiminished: () -> Unit, onDiminished: () -> Unit,
onHp: () -> Unit, onHp: () -> Unit,

View file

@ -0,0 +1,39 @@
package com.pixelized.desktop.lwa.ui.screen.campaign.player.detail
import com.pixelized.desktop.lwa.business.ExpressionUseCase
import com.pixelized.desktop.lwa.parser.expression.Expression
import com.pixelized.desktop.lwa.repository.alteration.model.FieldAlteration
import com.pixelized.shared.lwa.model.characterSheet.CharacterSheet
import com.pixelized.shared.lwa.model.characterSheet.CharacterSheet.CharacteristicId.HP
import com.pixelized.shared.lwa.model.characterSheet.CharacterSheet.CharacteristicId.MOV
import com.pixelized.shared.lwa.model.characterSheet.CharacterSheet.CharacteristicId.PORTRAIT
import com.pixelized.shared.lwa.model.characterSheet.CharacterSheet.CharacteristicId.PP
class CharacterDetailFactory(
private val expressionUseCase: ExpressionUseCase,
) {
fun convertToCharacterDetailHeaderUio(
sheet: CharacterSheet?,
alterations: Map<String, List<FieldAlteration>>,
): CharacterDetailHeaderUio? {
if (sheet == null) return null
fun List<FieldAlteration>?.sum(): Int {
return this?.sumOf {
expressionUseCase.computeExpression(sheet = sheet, expression = it.expression)
} ?: 0
}
return CharacterDetailHeaderUio(
id = sheet.id,
portrait = alterations[PORTRAIT]
?.firstNotNullOfOrNull { (it.expression as? Expression.UrlExpression)?.url }
?: sheet.portrait,
name = sheet.name,
hp = "${sheet.hp + alterations[HP].sum()}",
pp = "${sheet.pp + alterations[PP].sum()}",
mov = "${sheet.movement + alterations[MOV].sum()}"
)
}
}

View file

@ -13,33 +13,43 @@ import com.pixelized.desktop.lwa.repository.campaign.CampaignRepository
import com.pixelized.desktop.lwa.repository.characterSheet.CharacterSheetRepository import com.pixelized.desktop.lwa.repository.characterSheet.CharacterSheetRepository
import com.pixelized.shared.lwa.model.campaign.damage import com.pixelized.shared.lwa.model.campaign.damage
import com.pixelized.shared.lwa.model.campaign.power import com.pixelized.shared.lwa.model.campaign.power
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.mapNotNull import kotlinx.coroutines.flow.emptyFlow
import kotlinx.coroutines.flow.flatMap
import kotlinx.coroutines.flow.flatMapConcat
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.flow.stateIn
import androidx.compose.runtime.collectAsState
import kotlinx.coroutines.flow.map
class CharacterDetailViewModel( class CharacterDetailViewModel(
private val characterRepository: CharacterSheetRepository, private val characterRepository: CharacterSheetRepository,
private val campaignRepository: CampaignRepository, private val campaignRepository: CampaignRepository,
private val alterationRepository: AlterationRepository, private val alterationRepository: AlterationRepository,
private val characterDetailFactory: CharacterDetailFactory,
) : ViewModel() { ) : ViewModel() {
private val displayedCharacterId = MutableStateFlow<String?>(null) private val displayedCharacterId = MutableStateFlow<String?>(null)
val detail: StateFlow<CharacterDetailUio?> = displayedCharacterId.map { id -> @OptIn(ExperimentalCoroutinesApi::class)
val sheet = characterRepository.characterDetail(id) ?: return@map null val detail: StateFlow<CharacterDetailHeaderUio?> = displayedCharacterId.flatMapLatest { id ->
CharacterDetailUio( if (id != null) {
id = sheet.id, combine(
portrait = sheet.portrait, characterRepository.characterDetailFlow(characterId = id),
name = sheet.name, alterationRepository.alterationsFlow(characterId = id),
hp = "${sheet.hp}", ) { sheet, alteration ->
pp = "${sheet.pp}", characterDetailFactory.convertToCharacterDetailHeaderUio(
mov = "${sheet.movement}" sheet = sheet,
) alterations = alteration,
)
}
} else {
flowOf(null)
}
}.stateIn( }.stateIn(
scope = viewModelScope, scope = viewModelScope,
started = SharingStarted.Eagerly, started = SharingStarted.Eagerly,
@ -48,15 +58,15 @@ class CharacterDetailViewModel(
@Composable @Composable
@Stable @Stable
fun collectDynamicDetailAsState(id: String): State<CharacterDynDetailUio?> { fun collectDynamicDetailAsState(id: String): State<CharacterDetailHeaderInstanceUio?> {
val scope = rememberCoroutineScope() val scope = rememberCoroutineScope()
val flow: StateFlow<CharacterDynDetailUio?> = remember(id) { val flow: StateFlow<CharacterDetailHeaderInstanceUio?> = remember(id) {
combine( combine(
characterRepository.characterDetailFlow(id), characterRepository.characterDetailFlow(id),
campaignRepository.characterInstanceFlow(id = id), campaignRepository.characterInstanceFlow(id = id),
) { sheet, instance -> ) { sheet, instance ->
if (sheet == null) return@combine null if (sheet == null) return@combine null
CharacterDynDetailUio( CharacterDetailHeaderInstanceUio(
hp = "${sheet.hp - instance.damage}", hp = "${sheet.hp - instance.damage}",
pp = "${sheet.power - instance.power}", pp = "${sheet.power - instance.power}",
) )

View file

@ -1,24 +1,48 @@
package com.pixelized.desktop.lwa.ui.screen.campaign.player.ribbon package com.pixelized.desktop.lwa.ui.screen.campaign.player.ribbon
import com.pixelized.desktop.lwa.business.ExpressionUseCase
import com.pixelized.desktop.lwa.parser.expression.Expression
import com.pixelized.desktop.lwa.repository.alteration.model.FieldAlteration
import com.pixelized.shared.lwa.model.campaign.Campaign import com.pixelized.shared.lwa.model.campaign.Campaign
import com.pixelized.shared.lwa.model.campaign.damage import com.pixelized.shared.lwa.model.campaign.damage
import com.pixelized.shared.lwa.model.campaign.power import com.pixelized.shared.lwa.model.campaign.power
import com.pixelized.shared.lwa.model.characterSheet.CharacterSheet import com.pixelized.shared.lwa.model.characterSheet.CharacterSheet
import com.pixelized.shared.lwa.model.characterSheet.CharacterSheet.CharacteristicId.HP
import com.pixelized.shared.lwa.model.characterSheet.CharacterSheet.CharacteristicId.PP
import com.pixelized.shared.lwa.model.characterSheet.CharacterSheet.CharacteristicId.THUMBNAIL
class PlayerRibbonFactory { class PlayerRibbonFactory(
private val expressionUseCase: ExpressionUseCase,
) {
fun convertToPlayerPortraitUio( fun convertToPlayerPortraitUio(
characterSheet: CharacterSheet?, characterSheet: CharacterSheet?,
characterInstance: Campaign.CharacterInstance, characterInstance: Campaign.CharacterInstance,
alterations: Map<String, List<FieldAlteration>>,
): PlayerPortraitUio? { ): PlayerPortraitUio? {
if (characterSheet == null) return null if (characterSheet == null) return null
fun List<FieldAlteration>?.sum(): Int {
return this?.sumOf {
expressionUseCase.computeExpression(
sheet = characterSheet,
expression = it.expression,
)
} ?: 0
}
val maxHp = characterSheet.hp + alterations[HP].sum()
val maxPp = characterSheet.pp + alterations[PP].sum()
return PlayerPortraitUio( return PlayerPortraitUio(
id = characterSheet.id, id = characterSheet.id,
portrait = characterSheet.thumbnail, portrait = alterations[THUMBNAIL]
hp = characterSheet.hp - characterInstance.damage, ?.firstNotNullOfOrNull { (it.expression as? Expression.UrlExpression)?.url }
maxHp = characterSheet.hp, ?: characterSheet.thumbnail,
pp = characterSheet.pp - characterInstance.power, hp = maxHp - characterInstance.damage,
maxPp = characterSheet.pp, maxHp = maxHp,
pp = maxPp - characterInstance.power,
maxPp = maxPp,
) )
} }
} }

View file

@ -8,30 +8,49 @@ import androidx.compose.runtime.State
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope 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.campaign.CampaignRepository
import com.pixelized.desktop.lwa.repository.characterSheet.CharacterSheetRepository import com.pixelized.desktop.lwa.repository.characterSheet.CharacterSheetRepository
import com.pixelized.desktop.lwa.repository.roll_history.RollHistoryRepository import com.pixelized.desktop.lwa.repository.roll_history.RollHistoryRepository
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.flatMapMerge
import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.launch
class PlayerRibbonViewModel( class PlayerRibbonViewModel(
private val rollHistoryRepository: RollHistoryRepository, private val rollHistoryRepository: RollHistoryRepository,
characterRepository: CharacterSheetRepository, characterRepository: CharacterSheetRepository,
alterationRepository: AlterationRepository,
private val ribbonFactory: PlayerRibbonFactory, private val ribbonFactory: PlayerRibbonFactory,
campaignRepository: CampaignRepository, campaignRepository: CampaignRepository,
) : ViewModel() { ) : ViewModel() {
@OptIn(ExperimentalCoroutinesApi::class)
val characters: StateFlow<List<PlayerPortraitUio>> = campaignRepository.campaignFlow val characters: StateFlow<List<PlayerPortraitUio>> = campaignRepository.campaignFlow
.map { campaign -> .flatMapMerge { campaign ->
campaign.characters.mapNotNull { entry -> combine<PlayerPortraitUio?, List<PlayerPortraitUio>>(
ribbonFactory.convertToPlayerPortraitUio( flows = campaign.characters.map { entry ->
characterSheet = characterRepository.characterDetail(characterId = entry.key), combine(
characterInstance = entry.value, characterRepository.characterDetailFlow(characterId = entry.key),
) alterationRepository.alterationsFlow(characterId = entry.key),
} ) { sheet, alterations ->
}.stateIn( ribbonFactory.convertToPlayerPortraitUio(
characterSheet = sheet,
characterInstance = entry.value,
alterations = alterations,
)
}
},
transform = { headers ->
headers.mapNotNull { it }.toList()
}
)
}
.stateIn(
scope = viewModelScope, scope = viewModelScope,
started = SharingStarted.Eagerly, started = SharingStarted.Eagerly,
initialValue = emptyList() initialValue = emptyList()
@ -39,6 +58,19 @@ class PlayerRibbonViewModel(
private val rolls = hashMapOf<String, MutableState<PlayerPortraitRollUio?>>() private val rolls = hashMapOf<String, MutableState<PlayerPortraitRollUio?>>()
init {
viewModelScope.launch {
campaignRepository.campaignFlow.collectLatest {
it.characters.keys.forEach { id ->
characterRepository.characterDetail(
characterId = id,
forceUpdate = true,
)
}
}
}
}
@Composable @Composable
@Stable @Stable
fun roll(characterId: String): State<PlayerPortraitRollUio?> { fun roll(characterId: String): State<PlayerPortraitRollUio?> {

View file

@ -46,7 +46,6 @@ class CharacterSheetFactory(
private val skillUseCase: ExpressionUseCase, private val skillUseCase: ExpressionUseCase,
private val expressionUseCase: ExpressionUseCase, private val expressionUseCase: ExpressionUseCase,
) { ) {
suspend fun convertToUio( suspend fun convertToUio(
sheet: CharacterSheet?, sheet: CharacterSheet?,
campaign: Campaign, campaign: Campaign,

View file

@ -73,7 +73,7 @@ class CharacterSheetViewModel(
private val sheetFlow = combine( private val sheetFlow = combine(
characterRepository.characterDetailFlow(characterId = argument.id), characterRepository.characterDetailFlow(characterId = argument.id),
campaignRepository.campaignFlow, campaignRepository.campaignFlow,
alteration.alterations(characterId = argument.id), alteration.alterationsFlow(characterId = argument.id),
transform = { sheet, campaign, alterations -> transform = { sheet, campaign, alterations ->
factory.convertToUio( factory.convertToUio(
sheet = sheet, sheet = sheet,

View file

@ -1,5 +1,6 @@
package com.pixelized.desktop.lwa.business package com.pixelized.desktop.lwa.business
import com.pixelized.shared.lwa.usecase.CharacterSheetUseCase
import org.junit.Test import org.junit.Test
class DamageBonusUseCaseTest { class DamageBonusUseCaseTest {

View file

@ -1,5 +1,6 @@
package com.pixelized.desktop.lwa.business package com.pixelized.desktop.lwa.business
import com.pixelized.shared.lwa.usecase.CharacterSheetUseCase
import org.junit.Test import org.junit.Test
class SkillNormalizerUseCaseText { class SkillNormalizerUseCaseText {

View file

@ -62,6 +62,26 @@ class ExpressionParserTest {
) )
} }
@Test
fun textUrlExpression() {
val parser = ExpressionParser(
diceParser = DiceParser(),
wordParser = WordParser(),
)
parser.test(
expression = "https://drive.google.com/uc?export=view&id=19ME-r7LiDQSrDCq8faVlTNQ7tfX8UUOO",
expected = Expression.UrlExpression("https://drive.google.com/uc?export=view&id=19ME-r7LiDQSrDCq8faVlTNQ7tfX8UUOO"),
)
parser.test(
expression = "www.google.fr",
expected = Expression.UrlExpression("www.google.fr"),
)
parser.test(
expression = "google.fr",
expected = Expression.UrlExpression("google.fr"),
)
}
@Test @Test
fun testFunctionExpression() { fun testFunctionExpression() {
val parser = ExpressionParser( val parser = ExpressionParser(

View file

@ -48,6 +48,26 @@ data class CharacterSheet(
val roll: String, val roll: String,
) )
object CharacteristicId {
const val PORTRAIT = "PORTRAIT"
const val THUMBNAIL = "THUMBNAIL"
const val LVL = "LEVEL"
const val STR = "STR"
const val DEX = "DEX"
const val CON = "CON"
const val HEI = "HEI"
const val INT = "INT"
const val POW = "POW"
const val CHA = "CHA"
const val MOV = "MOV"
const val HP = "HP"
const val PP = "PP"
const val DMG = "DMG"
const val ARMOR = "ARMOR"
const val LB = "LEARNING"
const val GHP = "HP_GROW"
}
object CommonSkillId { object CommonSkillId {
const val COMBAT_ID = "COMBAT" const val COMBAT_ID = "COMBAT"
const val DODGE_ID = "DODGE" const val DODGE_ID = "DODGE"
@ -66,21 +86,4 @@ data class CharacterSheet(
const val SLEIGHT_OF_HAND_ID = "SLEIGHT_OF_HAND" const val SLEIGHT_OF_HAND_ID = "SLEIGHT_OF_HAND"
const val AID_ID = "AID" const val AID_ID = "AID"
} }
object CharacteristicId {
const val STR = "STR"
const val DEX = "DEX"
const val CON = "CON"
const val HEI = "HEI"
const val INT = "INT"
const val POW = "POW"
const val CHA = "CHA"
const val MOV = "MOV"
const val HP = "HP"
const val PP = "PP"
const val DMG = "DMG"
const val ARMOR = "ARMOR"
const val LB = "LEARNING"
const val GHP = "HP_GROW"
}
} }