Add death saving throw management.

This commit is contained in:
Thomas Andres Gomez 2023-11-05 22:03:15 +01:00
parent d38caea015
commit 96149b951a
15 changed files with 367 additions and 27 deletions

View file

@ -173,6 +173,15 @@ class DiceThrowUseCase @Inject constructor(
relatedStat = Property.CHARISMA
)
is DiceThrow.DeathSavingThrow -> savingThrow(
character = sheet,
alterations = alterations,
abilityLabel = { getString(R.string.character_sheet_stat_death) },
relatedLabel = { getString(R.string.character_sheet_stat_death) },
ability = Property.DEATH_SAVING_THROW,
relatedStat = null,
)
is DiceThrow.Acrobatics -> abilityThrow(
character = sheet,
alterations = alterations,
@ -494,7 +503,7 @@ class DiceThrowUseCase @Inject constructor(
abilityLabel: Context.() -> String,
relatedLabel: Context.() -> String?,
ability: Property,
relatedStat: Property,
relatedStat: Property?,
): DiceThrowResult = rollAbility(
character = character,
alterations = alterations,
@ -514,7 +523,7 @@ class DiceThrowUseCase @Inject constructor(
relatedTitle: Context.(label: String) -> String?,
relatedLabel: Context.() -> String?,
ability: Property,
relatedStat: Property,
relatedStat: Property?,
): DiceThrowResult {
// retrieve some wording.
val abilityLabelString = abilityLabel(application)

View file

@ -11,6 +11,10 @@ data class CharacterSheetFire(
@set:PropertyName(HIT_POINT)
var hitPoint: HitPoint? = null,
@get:PropertyName(DEATH)
@set:PropertyName(DEATH)
var death: Death? = null,
@get:PropertyName(SKILLS)
@set:PropertyName(SKILLS)
var skills: Map<String, Int> = emptyMap(),
@ -31,8 +35,21 @@ data class CharacterSheetFire(
var value: Int? = null,
)
@Keep
@IgnoreExtraProperties
data class Death(
@get:PropertyName("success")
@set:PropertyName("success")
var success: Int? = null,
@get:PropertyName("failure")
@set:PropertyName("failure")
var failure: Int? = null,
)
companion object {
const val HIT_POINT = "hit_point"
const val DEATH = "death"
const val SKILLS = "skills"
const val SPELLS = "spells"
const val SPELL_PREFIX = "lvl_"

View file

@ -14,6 +14,7 @@ sealed class DiceThrow(val character: String) {
class IntelligenceSavingThrow(character: String) : DiceThrow(character)
class WisdomSavingThrow(character: String) : DiceThrow(character)
class CharismaSavingThrow(character: String) : DiceThrow(character)
class DeathSavingThrow(character: String): DiceThrow(character)
class Acrobatics(character: String) : DiceThrow(character)
class AnimalHandling(character: String) : DiceThrow(character)
class Arcana(character: String) : DiceThrow(character)

View file

@ -23,6 +23,7 @@ enum class Property(val key: String) {
INTELLIGENCE_SAVING_THROW("Jet de sauvegarde: Intelligence"),
WISDOM_SAVING_THROW("Jet de sauvegarde: Sagesse"),
CHARISMA_SAVING_THROW("Jet de sauvegarde: Charisme"),
DEATH_SAVING_THROW("Jet de sauvegarde: Mort"),
ACROBATICS("Acrobaties"),
ANIMAL_HANDLING("Dressage"),
ARCANA("Arcanes"),

View file

@ -75,6 +75,18 @@ class FirebaseRepository @Inject constructor(
)
}
fun setCharacterDeathCounter(character: String, success: Int, failure: Int) {
val reference = database.getReference(
"$PATH_CHARACTERS/$character/${CharacterSheetFire.DEATH}"
)
reference.setValue(
CharacterSheetFire.Death(
success = success,
failure = failure,
)
)
}
fun setSkill(character: String, name: String, value: Int) {
val reference = database.getReference(
"$PATH_CHARACTERS/$character/${CharacterSheetFire.SKILLS}/$name"

View file

@ -67,10 +67,10 @@ import com.pixelized.rplexicon.ui.composable.Loader
import com.pixelized.rplexicon.ui.composable.edit.HandleHitPointEditDialog
import com.pixelized.rplexicon.ui.composable.error.HandleFetchError
import com.pixelized.rplexicon.ui.navigation.LocalScreenNavHost
import com.pixelized.rplexicon.ui.screens.character.CharacterHeader.Action
import com.pixelized.rplexicon.ui.screens.character.CharacterHeader.Alteration
import com.pixelized.rplexicon.ui.screens.character.CharacterHeader.Inventory
import com.pixelized.rplexicon.ui.screens.character.CharacterHeader.Proficiency
import com.pixelized.rplexicon.ui.screens.character.CharacterTabUio.Action
import com.pixelized.rplexicon.ui.screens.character.CharacterTabUio.Alteration
import com.pixelized.rplexicon.ui.screens.character.CharacterTabUio.Inventory
import com.pixelized.rplexicon.ui.screens.character.CharacterTabUio.Proficiency
import com.pixelized.rplexicon.ui.screens.character.composable.character.CharacterSheetHeader
import com.pixelized.rplexicon.ui.screens.character.composable.character.CharacterSheetHeaderUio
import com.pixelized.rplexicon.ui.screens.character.composable.character.ProficiencyUio.ID.*
@ -99,7 +99,7 @@ import com.pixelized.rplexicon.utilitary.extentions.lexiconShadow
import kotlinx.coroutines.launch
enum class CharacterHeader(@StringRes val label: Int) {
enum class CharacterTabUio(@StringRes val label: Int) {
Action(R.string.character_sheet_tab_actions),
Alteration(R.string.character_sheet_tab_alteration),
Inventory(R.string.character_sheet_tab_inventory),
@ -154,6 +154,12 @@ fun CharacterSheetScreen(
scope.launch { pagerState.animateScrollToPage(it) }
},
onHitPoint = headerViewModel::toggleHitPointDialog,
onDeathRoll = {
overlay.prepareRoll(diceThrow = headerViewModel.onDeathThrow())
overlay.showOverlay()
},
onDeathSuccess = headerViewModel::onDeathSuccess,
onDeathFailure = headerViewModel::onDeathFailure,
onRefresh = {
scope.launch { viewModel.update(force = true) }
},
@ -238,9 +244,12 @@ private fun CharacterSheetContent(
onRefresh: () -> Unit,
onFullRefresh: () -> Unit,
name: String,
tabs: State<List<CharacterHeader>>,
tabs: State<List<CharacterTabUio>>,
header: State<CharacterSheetHeaderUio?>,
onHitPoint: () -> Unit,
onDeathRoll: () -> Unit,
onDeathSuccess: () -> Unit,
onDeathFailure: () -> Unit,
onBack: () -> Unit,
onTab: (Int) -> Unit,
loader: @Composable BoxScope.() -> Unit,
@ -314,6 +323,9 @@ private fun CharacterSheetContent(
modifier = Modifier.fillMaxWidth(),
header = header,
onHitPoint = onHitPoint,
onDeathRoll = onDeathRoll,
onDeathSuccess = onDeathSuccess,
onDeathFailure = onDeathFailure,
)
PagerHeader(
pagerState = pagerState,
@ -353,7 +365,7 @@ private fun CharacterSheetContent(
private fun PagerHeader(
modifier: Modifier = Modifier,
pagerState: PagerState,
tabs: State<List<CharacterHeader>>,
tabs: State<List<CharacterTabUio>>,
onTab: (Int) -> Unit,
) {
if (tabs.value.isNotEmpty()) {
@ -390,10 +402,10 @@ private fun rememberHeaderTabsState(
spellsViewModel: SpellsViewModel = hiltViewModel(),
skillViewModel: SkillsViewModel = hiltViewModel(),
alterationsViewModel: AlterationViewModel = hiltViewModel(),
): State<List<CharacterHeader>> {
): State<List<CharacterTabUio>> {
val headers = remember {
derivedStateOf {
mutableListOf<CharacterHeader>().apply {
mutableListOf<CharacterTabUio>().apply {
addAll(
when {
proficiencyViewModel.sheet.value != null -> listOf(Proficiency)
@ -450,6 +462,9 @@ private fun CharacterScreenPreview(
onFullRefresh = { },
loader = { },
onHitPoint = { },
onDeathRoll = { },
onDeathSuccess = { },
onDeathFailure = { },
proficiencies = { ProficiencyPreview() },
actions = { ActionPagePreview() },
alterations = { AlterationPagePreview() },
@ -462,7 +477,7 @@ private fun CharacterScreenPreview(
@Composable
@Stable
private fun rememberHeaderPreview(): State<List<CharacterHeader>> = remember {
private fun rememberHeaderPreview(): State<List<CharacterTabUio>> = remember {
mutableStateOf(
listOf(
Proficiency,

View file

@ -2,13 +2,13 @@ package com.pixelized.rplexicon.ui.screens.character.composable.character
import android.content.res.Configuration.UI_MODE_NIGHT_NO
import android.content.res.Configuration.UI_MODE_NIGHT_YES
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.Divider
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface
import androidx.compose.runtime.Composable
import androidx.compose.runtime.Stable
@ -18,8 +18,8 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import com.pixelized.rplexicon.ui.screens.character.composable.preview.rememberCharacterHeaderStatePreview
import com.pixelized.rplexicon.ui.screens.character.composable.preview.rememberDeathThrowUio
import com.pixelized.rplexicon.ui.theme.LexiconTheme
import com.pixelized.rplexicon.utilitary.extentions.lexicon
@Stable
data class CharacterSheetHeaderUio(
@ -27,23 +27,28 @@ data class CharacterSheetHeaderUio(
val hitPoint: LabelPointUio,
val speed: LabelPointUio,
val dC: LabelPointUio?,
val death: DeathThrowUio? = null
)
@Composable
fun CharacterSheetHeader(
modifier: Modifier = Modifier,
padding : PaddingValues = PaddingValues(start = 16.dp, end = 16.dp, bottom = 4.dp),
padding: PaddingValues = PaddingValues(start = 16.dp, end = 16.dp, bottom = 4.dp),
header: State<CharacterSheetHeaderUio?>,
onHitPoint : () -> Unit,
onHitPoint: () -> Unit,
onDeathRoll: () -> Unit,
onDeathSuccess: () -> Unit,
onDeathFailure: () -> Unit,
) {
Surface(
modifier = modifier,
) {
Column(
modifier = Modifier.padding(padding),
verticalArrangement = Arrangement.spacedBy(space = 4.dp),
horizontalAlignment = Alignment.CenterHorizontally,
) {
Row(
modifier = Modifier.padding(padding),
verticalAlignment = Alignment.Bottom,
horizontalArrangement = Arrangement.spacedBy(
space = 16.dp,
@ -63,6 +68,17 @@ fun CharacterSheetHeader(
LabelPoint(label = it)
}
}
AnimatedVisibility(
visible = header.value?.death != null,
) {
DeathHeader(
death = header.value?.death,
onDeathRoll = onDeathRoll,
onDeathSuccess = onDeathSuccess,
onDeathFailure = onDeathFailure,
)
}
}
}
}
@ -74,8 +90,14 @@ private fun CharacterSheetHeaderPreview() {
LexiconTheme {
Surface {
CharacterSheetHeader(
header = rememberCharacterHeaderStatePreview(),
modifier = Modifier.fillMaxWidth(),
header = rememberCharacterHeaderStatePreview(
death = rememberDeathThrowUio(),
),
onHitPoint = { },
onDeathRoll = { },
onDeathSuccess = { },
onDeathFailure = { },
)
}
}

View file

@ -0,0 +1,198 @@
package com.pixelized.rplexicon.ui.screens.character.composable.character
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.fadeIn
import androidx.compose.animation.fadeOut
import androidx.compose.animation.scaleIn
import androidx.compose.animation.scaleOut
import androidx.compose.foundation.background
import androidx.compose.foundation.border
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.IntrinsicSize
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.material3.Divider
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.Stable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.platform.LocalLayoutDirection
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.LayoutDirection
import androidx.compose.ui.unit.dp
import com.pixelized.rplexicon.R
import com.pixelized.rplexicon.ui.theme.LexiconTheme
@Stable
data class DeathThrowUio(
val success: Int,
val failure: Int,
)
@Composable
fun DeathHeader(
death: DeathThrowUio?,
onDeathSuccess: () -> Unit,
onDeathRoll: () -> Unit,
onDeathFailure: () -> Unit,
) {
Row(
modifier = Modifier.height(intrinsicSize = IntrinsicSize.Min),
horizontalArrangement = Arrangement.Center,
) {
Box(
modifier = Modifier.fillMaxHeight(),
) {
DeathMark(
modifier = Modifier
.align(alignment = Alignment.Center)
.clip(shape = CircleShape)
.clickable(onClick = onDeathSuccess)
.padding(all = 8.dp),
counter = death?.success ?: 0,
leftToRight = false,
)
Text(
modifier = Modifier
.align(alignment = Alignment.BottomEnd)
.padding(horizontal = 8.dp),
style = MaterialTheme.typography.labelSmall,
fontWeight = FontWeight.Light,
text = stringResource(id = R.string.generic_success),
)
}
Icon(
modifier = Modifier
.clip(shape = CircleShape)
.clickable(onClick = onDeathRoll)
.padding(all = 8.dp)
.size(size = 42.dp),
tint = MaterialTheme.colorScheme.primary,
painter = painterResource(id = R.drawable.ic_d20_24),
contentDescription = null
)
Box(
modifier = Modifier.fillMaxHeight(),
) {
DeathMark(
modifier = Modifier
.align(alignment = Alignment.Center)
.clip(shape = CircleShape)
.clickable(onClick = onDeathFailure)
.padding(all = 8.dp),
counter = death?.failure ?: 0,
leftToRight = true,
)
Text(
modifier = Modifier
.align(alignment = Alignment.BottomStart)
.padding(horizontal = 8.dp),
style = MaterialTheme.typography.labelSmall,
fontWeight = FontWeight.Light,
text = stringResource(id = R.string.generic_failure),
)
}
}
}
@Composable
private fun DeathMark(
modifier: Modifier = Modifier,
leftToRight: Boolean,
counter: Int,
) {
CompositionLocalProvider(
LocalLayoutDirection provides when (leftToRight) {
true -> LayoutDirection.Ltr
else -> LayoutDirection.Rtl
}
) {
Row(
modifier = modifier,
verticalAlignment = Alignment.CenterVertically,
) {
Circle(
full = counter > 0,
)
Divider(
modifier = Modifier.width(width = 8.dp),
thickness = 2.dp,
color = MaterialTheme.colorScheme.onSurface,
)
Circle(
full = counter > 1,
)
Divider(
modifier = Modifier.width(width = 8.dp),
thickness = 2.dp,
color = MaterialTheme.colorScheme.onSurface,
)
Circle(
full = counter > 2,
)
}
}
}
@Composable
private fun Circle(
modifier: Modifier = Modifier,
full: Boolean,
) {
Box(
modifier = modifier
.size(size = 24.dp)
.border(
width = 2.dp,
shape = CircleShape,
color = MaterialTheme.colorScheme.onSurface,
),
contentAlignment = Alignment.Center,
) {
AnimatedVisibility(
visible = full,
enter = fadeIn() + scaleIn(),
exit = fadeOut() + scaleOut(),
) {
Box(
modifier = Modifier
.size(size = 14.dp)
.background(
color = MaterialTheme.colorScheme.primary,
shape = CircleShape,
),
)
}
}
}
@Composable
@Preview
private fun DeathMarkPreview() {
LexiconTheme {
Surface {
DeathHeader(
death = DeathThrowUio(success = 2, failure = 1),
onDeathRoll = { },
onDeathSuccess = { },
onDeathFailure = { },
)
}
}
}

View file

@ -6,11 +6,14 @@ import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import com.pixelized.rplexicon.R
import com.pixelized.rplexicon.ui.screens.character.composable.character.CharacterSheetHeaderUio
import com.pixelized.rplexicon.ui.screens.character.composable.character.DeathThrowUio
import com.pixelized.rplexicon.ui.screens.character.composable.character.LabelPointUio
@Composable
@Stable
fun rememberCharacterHeaderStatePreview() = remember {
fun rememberCharacterHeaderStatePreview(
death: DeathThrowUio? = null
) = remember {
mutableStateOf(
CharacterSheetHeaderUio(
armorClass = LabelPointUio(
@ -31,6 +34,18 @@ fun rememberCharacterHeaderStatePreview() = remember {
label = R.string.character_sheet_title_dc,
value = "13",
),
death = death,
)
)
}
@Composable
@Stable
fun rememberDeathThrowUio(): DeathThrowUio {
return remember {
DeathThrowUio(
success = 2,
failure = 1,
)
}
}

View file

@ -2,6 +2,7 @@ package com.pixelized.rplexicon.ui.screens.character.factory
import com.pixelized.rplexicon.R
import com.pixelized.rplexicon.ui.screens.character.composable.character.CharacterSheetHeaderUio
import com.pixelized.rplexicon.ui.screens.character.composable.character.DeathThrowUio
import com.pixelized.rplexicon.ui.screens.character.composable.character.LabelPointUio
import com.pixelized.rplexicon.ui.screens.character.pages.actions.HeaderViewModel
import javax.inject.Inject
@ -33,7 +34,15 @@ class CharacterSheetHeaderUioFactory @Inject constructor() {
label = R.string.character_sheet_title_dc,
value = "$it",
)
}
},
death = if (fireHeaderData?.hp == 0) {
DeathThrowUio(
success = fireHeaderData.deathSuccess,
failure = fireHeaderData.deathFailure,
)
} else {
null
},
)
}

View file

@ -7,6 +7,7 @@ import androidx.compose.runtime.mutableStateOf
import androidx.lifecycle.SavedStateHandle
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.pixelized.rplexicon.model.DiceThrow
import com.pixelized.rplexicon.model.Property
import com.pixelized.rplexicon.repository.authentication.FirebaseRepository
import com.pixelized.rplexicon.repository.data.character.AlterationRepository
@ -72,6 +73,8 @@ class HeaderViewModel @Inject constructor(
val data = FireHeaderData(
hp = sheets.hitPoint?.value ?: 1,
extra = sheets.hitPoint?.additional ?: 0,
deathSuccess = sheets.death?.success ?: 0,
deathFailure = sheets.death?.failure ?: 0,
)
withContext(Dispatchers.Main) {
fireData.value = data
@ -81,6 +84,26 @@ class HeaderViewModel @Inject constructor(
}
}
fun onDeathThrow(): DiceThrow {
return DiceThrow.DeathSavingThrow(character = character)
}
fun onDeathSuccess() {
firebaseRepository.setCharacterDeathCounter(
character = character,
success = ((fireData.value?.deathSuccess ?: 0) + 1) % 4,
failure = fireData.value?.deathFailure ?: 0,
)
}
fun onDeathFailure() {
firebaseRepository.setCharacterDeathCounter(
character = character,
success = fireData.value?.deathSuccess ?: 0,
failure = ((fireData.value?.deathFailure ?: 0) + 1) % 4,
)
}
fun toggleHitPointDialog() {
_hitPointDialog.value = if (_hitPointDialog.value == null) {
HpPointDialogUio(
@ -94,6 +117,15 @@ class HeaderViewModel @Inject constructor(
}
fun applyHitPointChange(hp: Int, extra: Int) {
val hpDownToZero = (fireData.value?.hp ?: 0) > 0 && hp == 0
val hpUpFromZero = (fireData.value?.hp ?: 0) == 0 && hp > 0
if (hpDownToZero || hpUpFromZero) {
firebaseRepository.setCharacterDeathCounter(
character = character,
success = 0,
failure = 0,
)
}
firebaseRepository.setCharacterHitPoint(
character = character,
value = hp,
@ -114,5 +146,7 @@ class HeaderViewModel @Inject constructor(
data class FireHeaderData(
val hp: Int,
val extra: Int,
val deathSuccess: Int,
val deathFailure: Int,
)
}

View file

@ -189,8 +189,8 @@ private fun Critical(
else -> MaterialTheme.colorScheme.onSurface
},
text = when (it) {
1 -> stringResource(id = R.string.generic_critical_success)
2 -> stringResource(id = R.string.generic_critical_failure)
1 -> stringResource(id = R.string.generic_success_critical)
2 -> stringResource(id = R.string.generic_failure_critical)
else -> ""
},
)

View file

@ -45,6 +45,7 @@ class AlterationFactory @Inject constructor(
is DiceThrow.IntelligenceSavingThrow -> listOf(Property.INTELLIGENCE_SAVING_THROW)
is DiceThrow.WisdomSavingThrow -> listOf(Property.WISDOM_SAVING_THROW)
is DiceThrow.CharismaSavingThrow -> listOf(Property.CHARISMA_SAVING_THROW)
is DiceThrow.DeathSavingThrow -> listOf(Property.DEATH_SAVING_THROW)
is DiceThrow.Acrobatics -> listOf(Property.ACROBATICS, Property.DEXTERITY)
is DiceThrow.AnimalHandling -> listOf(Property.ANIMAL_HANDLING, Property.WISDOM)
is DiceThrow.Arcana -> listOf(Property.ARCANA, Property.INTELLIGENCE)

View file

@ -18,8 +18,10 @@
<string name="error_structure_location">La structure des cartes semble avoir changé et n\'est plus compatible avec cette application.</string>
<string name="error_structure_quest">La structure des quêtes semble avoir changé et n\'est plus compatible avec cette application.</string>
<string name="generic_critical_success">SUCCÈS CRITIQUE</string>
<string name="generic_critical_failure">ÉCHEC CRITIQUE</string>
<string name="generic_success">Succès</string>
<string name="generic_success_critical">SUCCÈS CRITIQUE</string>
<string name="generic_failure">Échec</string>
<string name="generic_failure_critical">ÉCHEC CRITIQUE</string>
<string name="gender_male">Mâle</string>
<string name="gender_female">Femelle</string>
@ -116,6 +118,7 @@
<string name="character_sheet_stat_wisdom_short">SAG</string>
<string name="character_sheet_stat_charisma">Charisme</string>
<string name="character_sheet_stat_charisma_short">CHA</string>
<string name="character_sheet_stat_death">Mort</string>
<string name="character_sheet_proficiency">Talent</string>
<string name="character_sheet_proficiency_acrobatics">Acrobaties</string>
<string name="character_sheet_proficiency_animal_handling">Dressage</string>

View file

@ -18,8 +18,10 @@
<string name="error_structure_location">The location sheet structure appears to have changed and is no longer compatible with this application</string>
<string name="error_structure_quest">The quest sheet structure appears to have changed and is no longer compatible with this application</string>
<string name="generic_critical_success">CRITICAL SUCCESS</string>
<string name="generic_critical_failure">CRITICAL FAILURE</string>
<string name="generic_success">Success</string>
<string name="generic_success_critical">CRITICAL SUCCESS</string>
<string name="generic_failure">Failure</string>
<string name="generic_failure_critical">CRITICAL FAILURE</string>
<string name="gender_male">Male</string>
<string name="gender_female">Female</string>
@ -116,6 +118,7 @@
<string name="character_sheet_stat_wisdom_short">WIS</string>
<string name="character_sheet_stat_charisma">Charisma</string>
<string name="character_sheet_stat_charisma_short">CHA</string>
<string name="character_sheet_stat_death">Death</string>
<string name="character_sheet_proficiency">Proficiency</string>
<string name="character_sheet_proficiency_acrobatics">Acrobatics</string>
<string name="character_sheet_proficiency_animal_handling">Animal Handling</string>