Add flat value management for attack skill and spell.

This commit is contained in:
Andres Gomez, Thomas (ITDV RL) 2024-06-03 18:11:58 +02:00
parent 8e9529d4d0
commit 2cc1a0ea70
13 changed files with 262 additions and 119 deletions

View file

@ -843,7 +843,7 @@ class DiceThrowUseCase @Inject constructor(
)
// fetch and build a list of additional level effect.
val levelBonus = if (spell?.level != null) {
val levelBonus = if (spell?.level?.dice != null) {
((spell.spell.level + 1)..level).map {
val localRoll = roll(
amount = spell.level.dice.count,

View file

@ -3,7 +3,7 @@ package com.pixelized.rplexicon.data.model.roll
import com.pixelized.rplexicon.data.model.Property
class Throw(
val dice: Dice,
val dice: Dice?,
val flat: Flat?,
val modifier: List<Property>,
)

View file

@ -10,14 +10,11 @@ class ThrowParser @Inject constructor(
) {
fun parse(value: String?): Throw? {
if (value != null) {
val dice = diceParser.parse(value = value).firstOrNull()
if (dice != null) {
return Throw(
dice = dice,
flat = flatValueParser.parse(value = value),
modifier = modifierParser.parse(value = value),
)
}
return Throw(
dice = diceParser.parse(value = value).firstOrNull(),
flat = flatValueParser.parse(value = value),
modifier = modifierParser.parse(value = value),
)
}
return null
}

View file

@ -32,6 +32,7 @@ import com.pixelized.rplexicon.R
import com.pixelized.rplexicon.data.model.Attack
import com.pixelized.rplexicon.ui.composable.AsyncImage
import com.pixelized.rplexicon.ui.screens.character.composable.common.DiceButton
import com.pixelized.rplexicon.ui.screens.character.composable.common.FlatValue
import com.pixelized.rplexicon.ui.theme.LexiconTheme
import com.pixelized.rplexicon.utilitary.extentions.uri
@ -42,14 +43,22 @@ data class AttackUio(
val name: String,
@StringRes val type: Int,
val range: String?,
val hit: Dice?,
val damage: Dice?,
val hit: Action?,
val damage: Action?,
) {
@Stable
class Dice(
@DrawableRes val icon: Int,
val label: String,
)
sealed class Action {
@Stable
class Dice(
@DrawableRes val icon: Int,
val label: String,
) : Action()
@Stable
class Flat(
val label: String,
) : Action()
}
}
@Composable
@ -106,19 +115,31 @@ fun Attack(
Row(
horizontalArrangement = Arrangement.spacedBy(space = 4.dp),
) {
weapon.hit?.let { dice ->
DiceButton(
icon = dice.icon,
text = dice.label,
onClick = { weapon.name.let(onHit) }
)
weapon.hit?.let { action ->
when (action) {
is AttackUio.Action.Dice -> DiceButton(
icon = action.icon,
text = action.label,
onClick = { weapon.name.let(onHit) }
)
is AttackUio.Action.Flat -> FlatValue(
text = action.label,
)
}
}
weapon.damage?.let { dice ->
DiceButton(
icon = dice.icon,
text = dice.label,
onClick = { weapon.name.let(onDamage) }
)
weapon.damage?.let { action ->
when (action) {
is AttackUio.Action.Dice -> DiceButton(
icon = action.icon,
text = action.label,
onClick = { weapon.name.let(onDamage) }
)
is AttackUio.Action.Flat -> FlatValue(
text = action.label,
)
}
}
}
}
@ -147,19 +168,19 @@ private class WeaponPreviewProvider : PreviewParameterProvider<AttackUio> {
override val values: Sequence<AttackUio> = sequenceOf(
AttackUio(
icon = R.drawable.ic_crossed_swords_24.uri.toUri(),
name = "Dagger",
name = "Sans arme",
type = R.string.attack_type_melee,
range = "5 ft reach",
hit = AttackUio.Dice(icon = R.drawable.ic_d20_24, label = "1d20"),
damage = AttackUio.Dice(icon = R.drawable.ic_d8_24, label = "1d8"),
hit = AttackUio.Action.Dice(icon = R.drawable.ic_d20_24, label = "1d20"),
damage = AttackUio.Action.Flat(label = "1"),
),
AttackUio(
icon = R.drawable.ic_pocket_bow_24.uri.toUri(),
name = "Long bow",
type = R.string.attack_type_range,
range = "30 ft reach",
hit = AttackUio.Dice(icon = R.drawable.ic_d20_24, label = "1d20+5"),
damage = AttackUio.Dice(icon = R.drawable.ic_d8_24, label = "1d8+3"),
hit = AttackUio.Action.Dice(icon = R.drawable.ic_d20_24, label = "1d20+5"),
damage = AttackUio.Action.Dice(icon = R.drawable.ic_d8_24, label = "1d8+3"),
),
)
}

View file

@ -33,6 +33,7 @@ import com.pixelized.rplexicon.R
import com.pixelized.rplexicon.ui.composable.AsyncImage
import com.pixelized.rplexicon.ui.screens.character.composable.common.CounterButton
import com.pixelized.rplexicon.ui.screens.character.composable.common.DiceButton
import com.pixelized.rplexicon.ui.screens.character.composable.common.FlatValue
import com.pixelized.rplexicon.ui.theme.LexiconTheme
@Stable
@ -42,15 +43,24 @@ data class SkillItemUio(
val translate: String?,
val rest: String?,
val cost: String?,
val effect: Dice?,
val effect: Action?,
val value: Int?,
val max: Int?,
val haveDetail: Boolean,
) {
class Dice(
@DrawableRes val icon: Int,
val label: String,
)
@Stable
sealed class Action {
@Stable
class Dice(
@DrawableRes val icon: Int,
val label: String,
) : Action()
@Stable
class Flat(
val label: String,
) : Action()
}
}
@Composable
@ -131,11 +141,17 @@ fun SkillItem(
}
skill.effect?.let { effect ->
DiceButton(
icon = effect.icon,
text = effect.label,
onClick = { onThrow(skill) },
)
when (effect) {
is SkillItemUio.Action.Dice -> DiceButton(
icon = effect.icon,
text = effect.label,
onClick = { onThrow(skill) },
)
is SkillItemUio.Action.Flat -> FlatValue(
text = effect.label,
)
}
}
skill.max?.let {
@ -193,7 +209,7 @@ private class CounterItemPreviewProvider : PreviewParameterProvider<SkillItemUio
translate = "Mantle of Inspiration",
rest = null,
cost = "Inspiration bardique",
effect = SkillItemUio.Dice(icon = R.drawable.ic_d6_24, label = "2d6"),
effect = SkillItemUio.Action.Dice(icon = R.drawable.ic_d6_24, label = "2d6"),
value = null,
max = null,
haveDetail = true,

View file

@ -37,6 +37,7 @@ import com.pixelized.rplexicon.R
import com.pixelized.rplexicon.data.model.Spell
import com.pixelized.rplexicon.ui.composable.AsyncImage
import com.pixelized.rplexicon.ui.screens.character.composable.common.DiceButton
import com.pixelized.rplexicon.ui.screens.character.composable.common.FlatValue
import com.pixelized.rplexicon.ui.theme.LexiconTheme
import com.pixelized.rplexicon.utilitary.extentions.local.icon
import com.pixelized.rplexicon.utilitary.extentions.uri
@ -50,16 +51,25 @@ data class SpellUio(
val castingTime: String,
val range: String,
val duration: String,
val hit: Dice?,
val effect: Dice?,
val hit: Action?,
val effect: Action?,
val changeWithLevel: Boolean,
val isWarlock: Boolean,
val ritual: Boolean,
) {
class Dice(
@DrawableRes val icon: Int,
val label: String,
)
@Stable
sealed class Action {
@Stable
class Dice(
@DrawableRes val icon: Int,
val label: String,
) : Action()
@Stable
class Flat(
val label: String,
) : Action()
}
}
@OptIn(ExperimentalLayoutApi::class)
@ -173,14 +183,20 @@ fun Spell(
Row(
horizontalArrangement = Arrangement.spacedBy(space = 4.dp),
) {
spell.hit?.let { dice ->
DiceButton(
icon = dice.icon,
text = dice.label,
onClick = { spell.name.let(onHit) }
)
spell.hit?.let { action ->
when (action) {
is SpellUio.Action.Dice -> DiceButton(
icon = action.icon,
text = action.label,
onClick = { spell.name.let(onHit) }
)
is SpellUio.Action.Flat -> FlatValue(
text = action.label,
)
}
}
spell.effect?.let { dice ->
spell.effect?.let { action ->
when {
spell.changeWithLevel && spell.isWarlock.not() -> {
OutlinedButton(
@ -196,19 +212,33 @@ fun Spell(
)
}
}
spell.changeWithLevel && spell.isWarlock -> {
DiceButton(
icon = dice.icon,
text = dice.label,
onClick = { onCast(spell.name) },
)
when (action) {
is SpellUio.Action.Dice -> DiceButton(
icon = action.icon,
text = action.label,
onClick = { onCast(spell.name) },
)
is SpellUio.Action.Flat -> FlatValue(
text = action.label,
)
}
}
else -> {
DiceButton(
icon = dice.icon,
text = dice.label,
onClick = { spell.name.let(onEffect) }
)
when (action) {
is SpellUio.Action.Dice -> DiceButton(
icon = action.icon,
text = action.label,
onClick = { spell.name.let(onEffect) }
)
is SpellUio.Action.Flat -> FlatValue(
text = action.label,
)
}
}
}
}
@ -246,11 +276,11 @@ private class SpellPreviewProvider : PreviewParameterProvider<SpellUio> {
castingTime = "1 action",
range = "36 mêtres",
duration = "instantanée",
hit = SpellUio.Dice(
hit = SpellUio.Action.Dice(
icon = R.drawable.ic_d20_24,
label = "1d20+6",
),
effect = SpellUio.Dice(
effect = SpellUio.Action.Dice(
icon = R.drawable.ic_d10_24,
label = "1d10",
),
@ -267,7 +297,7 @@ private class SpellPreviewProvider : PreviewParameterProvider<SpellUio> {
range = "18 mêtres",
duration = "instantanée",
hit = null,
effect = SpellUio.Dice(
effect = SpellUio.Action.Dice(
icon = R.drawable.ic_d10_24,
label = "1d4",
),
@ -284,7 +314,7 @@ private class SpellPreviewProvider : PreviewParameterProvider<SpellUio> {
range = "contact",
duration = "instantanée",
hit = null,
effect = SpellUio.Dice(
effect = SpellUio.Action.Dice(
icon = R.drawable.ic_d8_24,
label = "1d8+3",
),
@ -301,7 +331,7 @@ private class SpellPreviewProvider : PreviewParameterProvider<SpellUio> {
range = "contact",
duration = "instantanée",
hit = null,
effect = SpellUio.Dice(
effect = SpellUio.Action.Dice(
icon = R.drawable.ic_d8_24,
label = "1d8+3",
),

View file

@ -0,0 +1,42 @@
package com.pixelized.rplexicon.ui.screens.character.composable.common
import android.content.res.Configuration
import androidx.compose.foundation.layout.Column
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.material3.minimumInteractiveComponentSize
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.tooling.preview.Preview
import com.pixelized.rplexicon.ui.theme.LexiconTheme
@Composable
fun FlatValue(
modifier: Modifier = Modifier,
text: String,
) {
Column(
modifier = modifier.minimumInteractiveComponentSize(),
horizontalAlignment = Alignment.CenterHorizontally,
) {
Text(
style = MaterialTheme.typography.titleLarge,
text = text,
)
}
}
@Composable
@Preview(uiMode = Configuration.UI_MODE_NIGHT_NO)
@Preview(uiMode = Configuration.UI_MODE_NIGHT_YES)
private fun DiceButtonPreview() {
LexiconTheme {
Surface {
FlatValue(
text = "5",
)
}
}
}

View file

@ -17,16 +17,15 @@ fun rememberAttackListStatePreview(): State<List<AttackUio>> = remember {
listOf(
AttackUio(
icon = R.drawable.ic_crossed_swords_24.uri.toUri(),
name = "Dagger",
name = "Unarmed attack",
type = R.string.attack_type_melee,
range = "5 ft reach",
hit = AttackUio.Dice(
hit = AttackUio.Action.Dice(
icon = R.drawable.ic_d20_24,
label = "1d20",
),
damage = AttackUio.Dice(
icon = R.drawable.ic_d8_24,
label = "1d8",
damage = AttackUio.Action.Flat(
label = "5",
),
),
AttackUio(
@ -34,11 +33,11 @@ fun rememberAttackListStatePreview(): State<List<AttackUio>> = remember {
name = "Long bow",
type = R.string.attack_type_range,
range = "30 ft reach",
hit = AttackUio.Dice(
hit = AttackUio.Action.Dice(
icon = R.drawable.ic_d20_24,
label = "1d20+5",
),
damage = AttackUio.Dice(
damage = AttackUio.Action.Dice(
icon = R.drawable.ic_d8_24,
label = "1d8+3"
),

View file

@ -30,11 +30,11 @@ fun rememberSpellListStatePreview(): State<List<Pair<SpellHeaderUio, List<SpellU
castingTime = "1 action",
range = "36 mêtres",
duration = "instantanée",
hit = SpellUio.Dice(
hit = SpellUio.Action.Dice(
icon = R.drawable.ic_d20_24,
label = "1d20+6",
),
effect = SpellUio.Dice(
effect = SpellUio.Action.Dice(
icon = R.drawable.ic_d10_24,
label = "1d10",
),
@ -51,7 +51,7 @@ fun rememberSpellListStatePreview(): State<List<Pair<SpellHeaderUio, List<SpellU
range = "18 mêtres",
duration = "instantanée",
hit = null,
effect = SpellUio.Dice(
effect = SpellUio.Action.Dice(
icon = R.drawable.ic_d10_24,
label = "1d4",
),
@ -73,7 +73,7 @@ fun rememberSpellListStatePreview(): State<List<Pair<SpellHeaderUio, List<SpellU
range = "contact",
duration = "instantanée",
hit = null,
effect = SpellUio.Dice(
effect = SpellUio.Action.Dice(
icon = R.drawable.ic_d8_24,
label = "1d8+3",
),

View file

@ -1,14 +1,14 @@
package com.pixelized.rplexicon.ui.screens.character.factory
import com.pixelized.rplexicon.R
import com.pixelized.rplexicon.data.model.alteration.Alteration
import com.pixelized.rplexicon.data.model.Attack
import com.pixelized.rplexicon.data.model.CharacterSheet
import com.pixelized.rplexicon.data.model.Property
import com.pixelized.rplexicon.data.model.alteration.Alteration
import com.pixelized.rplexicon.ui.screens.character.composable.actions.AttackUio
import com.pixelized.rplexicon.utilitary.extentions.icon
import com.pixelized.rplexicon.utilitary.extentions.toLabel
import com.pixelized.rplexicon.utilitary.extentions.modifier
import com.pixelized.rplexicon.utilitary.extentions.toLabel
import javax.inject.Inject
class AttackUioFactory @Inject constructor() {
@ -31,12 +31,18 @@ class AttackUioFactory @Inject constructor() {
Property.DEXTERITY -> characterSheet.dexterity.modifier
else -> 0
}
} + hitAlteration
} + hitAlteration + (diceThrow.flat?.value ?: 0)
// Build the UIO.
AttackUio.Dice(
icon = diceThrow.dice.icon,
label = "${diceThrow.dice.toLabel()}${modifier.toLabel(true)}",
)
when (diceThrow.dice) {
null -> AttackUio.Action.Flat(
label = "$modifier",
)
else -> AttackUio.Action.Dice(
icon = diceThrow.dice.icon,
label = "${diceThrow.dice.toLabel()}${modifier.toLabel(true)}",
)
}
}
val damage = attack.damage?.let { diceThrow ->
@ -52,12 +58,18 @@ class AttackUioFactory @Inject constructor() {
Property.DEXTERITY -> characterSheet.dexterity.modifier
else -> 0
}
} + damageAlteration
} + damageAlteration + (diceThrow.flat?.value ?: 0)
// Build the UIO.
AttackUio.Dice(
icon = diceThrow.dice.icon,
label = "${diceThrow.dice.toLabel()}${modifier.toLabel(true)}",
)
when (diceThrow.dice) {
null -> AttackUio.Action.Flat(
label = "$modifier",
)
else -> AttackUio.Action.Dice(
icon = diceThrow.dice.icon,
label = "${diceThrow.dice.toLabel()}${modifier.toLabel(true)}"
)
}
}
return AttackUio(

View file

@ -7,11 +7,11 @@ import com.pixelized.rplexicon.data.network.CharacterSheetFire
import com.pixelized.rplexicon.data.repository.character.DescriptionRepository
import com.pixelized.rplexicon.ui.screens.character.composable.actions.SkillItemUio
import com.pixelized.rplexicon.utilitary.extentions.icon
import com.pixelized.rplexicon.utilitary.extentions.toLabel
import com.pixelized.rplexicon.utilitary.extentions.local.base
import com.pixelized.rplexicon.utilitary.extentions.local.primary
import com.pixelized.rplexicon.utilitary.extentions.local.secondary
import com.pixelized.rplexicon.utilitary.extentions.modifier
import com.pixelized.rplexicon.utilitary.extentions.toLabel
import javax.inject.Inject
class SkillFactoryUioFactory @Inject constructor(
@ -49,11 +49,17 @@ class SkillFactoryUioFactory @Inject constructor(
translate = description?.original,
rest = skill.rest,
cost = skill.cost,
effect = skill.effect?.let {
SkillItemUio.Dice(
icon = it.dice.icon,
effect = when {
skill.effect?.dice != null -> SkillItemUio.Action.Dice(
icon = skill.effect.dice.icon,
label = "${skill.effect.dice.toLabel()}${modifier.toLabel(true)}",
)
skill.effect != null -> SkillItemUio.Action.Flat(
label = "$modifier",
)
else -> null
},
value = fire?.skills?.get(skill.name) ?: 0,
max = skill.amount,

View file

@ -1,13 +1,14 @@
package com.pixelized.rplexicon.ui.screens.character.factory
import com.pixelized.rplexicon.R
import com.pixelized.rplexicon.data.model.AssignedSpell
import com.pixelized.rplexicon.data.model.CharacterSheet
import com.pixelized.rplexicon.data.model.Property
import com.pixelized.rplexicon.data.repository.character.DescriptionRepository
import com.pixelized.rplexicon.ui.screens.character.composable.actions.SpellUio
import com.pixelized.rplexicon.utilitary.extentions.icon
import com.pixelized.rplexicon.utilitary.extentions.toLabel
import com.pixelized.rplexicon.utilitary.extentions.modifier
import com.pixelized.rplexicon.utilitary.extentions.toLabel
import javax.inject.Inject
class SpellUioFactory @Inject constructor(
@ -28,11 +29,17 @@ class SpellUioFactory @Inject constructor(
Property.CHARISMA -> characterSheet.charisma.modifier
else -> 0
}
} + (diceThrow.flat?.value ?: 0)
when (diceThrow.dice) {
null -> SpellUio.Action.Flat(
label = "$modifier",
)
else -> SpellUio.Action.Dice(
icon = diceThrow.dice.icon,
label = "${diceThrow.dice.toLabel()}${modifier.toLabel(true)}",
)
}
SpellUio.Dice(
icon = diceThrow.dice.icon,
label = "${diceThrow.dice.toLabel()}${modifier.toLabel(true)}",
)
}
val effect = assignedSpell.effect?.let { diceThrow ->
val modifier = diceThrow.modifier.sumOf {
@ -52,25 +59,38 @@ class SpellUioFactory @Inject constructor(
val delta = warlockSpellLevel?.minus(assignedSpell.spell.level) ?: 0
if (warlockSpellLevel == null || level == null || delta <= 0) {
// default case of non warlock character of the spell don't scale
SpellUio.Dice(
icon = diceThrow.dice.icon,
label = "${diceThrow.dice.toLabel()}${modifier.toLabel(true)}",
)
} else if (diceThrow.dice.faces == level.dice.faces) {
when (diceThrow.dice) {
null -> SpellUio.Action.Flat(
label = "$modifier",
)
else -> SpellUio.Action.Dice(
icon = diceThrow.dice.icon,
label = "${diceThrow.dice.toLabel()}${modifier.toLabel(true)}",
)
}
} else if (diceThrow.dice?.faces == level.dice?.faces) {
// warlock character, upscale the spell to warlock spell level
val upscaleModifier = modifier + (level.flat?.value ?: 0) * delta
val diceCount = diceThrow.dice.count + level.dice.count * delta
SpellUio.Dice(
icon = diceThrow.dice.icon,
label = "${diceCount}d${diceThrow.dice.faces}${upscaleModifier.toLabel(true)}",
)
val diceCount = (diceThrow.dice?.count ?: 0) + (level.dice?.count ?: 0) * delta
when (diceThrow.dice) {
null -> SpellUio.Action.Flat(
label = "$modifier",
)
else -> SpellUio.Action.Dice(
icon = diceThrow.dice.icon,
label = "${diceCount}d${diceThrow.dice.faces}${upscaleModifier.toLabel(true)}",
)
}
} else {
// warlock character, case where the dice use to upscale is not the one from the spell.
val deltaModifier = level.flat?.value?.times(delta) ?: 0
SpellUio.Dice(
icon = diceThrow.dice.icon,
label = "${diceThrow.dice.toLabel()}${modifier.toLabel(true)}" +
"+ ${level.dice.count * delta}d${level.dice.faces}${deltaModifier.toLabel(true)}",
SpellUio.Action.Dice(
icon = diceThrow.dice?.icon ?: R.drawable.ic_d4_24,
label = "${diceThrow.dice?.toLabel()}${modifier.toLabel(true)}" +
"+ ${level.dice?.count?.times(delta)}d${level.dice?.faces}${deltaModifier.toLabel(true)}",
)
}
}

View file

@ -180,7 +180,7 @@ class SpellsViewModel @Inject constructor(
val modifierLabel = modifier
.sumOf { modifier -> modifier.toValue(character = character) }
.let { if (it != 0) "${it.signLabel}${abs(it) * level}" else "" }
return when (level > 0) {
return when (level > 0 && dice != null) {
true -> "${dice.count * level}d${dice.faces}${modifierLabel}"
else -> null
}