Add Object action in the character action sheet.

This commit is contained in:
Thomas Andres Gomez 2023-10-24 18:08:25 +02:00
parent f9b3adfee0
commit cd921ac16f
21 changed files with 416 additions and 10 deletions

View file

@ -12,6 +12,7 @@ import com.pixelized.rplexicon.repository.data.character.CharacterSheetRepositor
import com.pixelized.rplexicon.repository.data.character.DescriptionRepository import com.pixelized.rplexicon.repository.data.character.DescriptionRepository
import com.pixelized.rplexicon.repository.data.character.EquipmentRepository import com.pixelized.rplexicon.repository.data.character.EquipmentRepository
import com.pixelized.rplexicon.repository.data.character.InventoryRepository import com.pixelized.rplexicon.repository.data.character.InventoryRepository
import com.pixelized.rplexicon.repository.data.character.ObjectActionRepository
import com.pixelized.rplexicon.repository.data.character.SkillRepository import com.pixelized.rplexicon.repository.data.character.SkillRepository
import com.pixelized.rplexicon.repository.data.character.SpellRepository import com.pixelized.rplexicon.repository.data.character.SpellRepository
import com.pixelized.rplexicon.repository.data.lexicon.LexiconRepository import com.pixelized.rplexicon.repository.data.lexicon.LexiconRepository
@ -35,6 +36,7 @@ class LauncherViewModel @Inject constructor(
alterationRepository: AlterationRepository, alterationRepository: AlterationRepository,
characterSheetRepository: CharacterSheetRepository, characterSheetRepository: CharacterSheetRepository,
actionRepository: ActionRepository, actionRepository: ActionRepository,
objectActionRepository: ObjectActionRepository,
spellRepository: SpellRepository, spellRepository: SpellRepository,
skillRepository: SkillRepository, skillRepository: SkillRepository,
descriptionRepository: DescriptionRepository, descriptionRepository: DescriptionRepository,
@ -124,6 +126,14 @@ class LauncherViewModel @Inject constructor(
_error.emit(FetchErrorUio.Structure(type = Type.ACTION)) _error.emit(FetchErrorUio.Structure(type = Type.ACTION))
} }
} }
val objects = async {
try {
objectActionRepository.fetchObjectAction()
} catch (exception: Exception) {
Log.e(TAG, exception.message, exception)
_error.emit(FetchErrorUio.Structure(type = Type.OBJECT))
}
}
val spell = async { val spell = async {
try { try {
spellRepository.fetchSpells() spellRepository.fetchSpells()
@ -142,7 +152,7 @@ class LauncherViewModel @Inject constructor(
} }
awaitAll(lexicon, location, quest) awaitAll(lexicon, location, quest)
awaitAll(description, inventory, equipment, alteration, action, spell, skill) awaitAll(description, inventory, equipment, alteration, action, objects, spell, skill)
isLoading = false isLoading = false
} }

View file

@ -13,6 +13,7 @@ import com.pixelized.rplexicon.model.Throw
import com.pixelized.rplexicon.repository.data.character.ActionRepository import com.pixelized.rplexicon.repository.data.character.ActionRepository
import com.pixelized.rplexicon.repository.data.character.AlterationRepository import com.pixelized.rplexicon.repository.data.character.AlterationRepository
import com.pixelized.rplexicon.repository.data.character.CharacterSheetRepository import com.pixelized.rplexicon.repository.data.character.CharacterSheetRepository
import com.pixelized.rplexicon.repository.data.character.ObjectActionRepository
import com.pixelized.rplexicon.repository.data.character.SkillRepository import com.pixelized.rplexicon.repository.data.character.SkillRepository
import com.pixelized.rplexicon.repository.data.character.SpellRepository import com.pixelized.rplexicon.repository.data.character.SpellRepository
import com.pixelized.rplexicon.ui.screens.rolls.composable.RollDiceUio import com.pixelized.rplexicon.ui.screens.rolls.composable.RollDiceUio
@ -42,6 +43,7 @@ class DiceThrowUseCase @Inject constructor(
private val application: Application, private val application: Application,
private val characterSheetRepository: CharacterSheetRepository, private val characterSheetRepository: CharacterSheetRepository,
private val actionRepository: ActionRepository, private val actionRepository: ActionRepository,
private val objectRepository: ObjectActionRepository,
private val spellRepository: SpellRepository, private val spellRepository: SpellRepository,
private val skillRepository: SkillRepository, private val skillRepository: SkillRepository,
private val alterationRepository: AlterationRepository, private val alterationRepository: AlterationRepository,
@ -393,6 +395,21 @@ class DiceThrowUseCase @Inject constructor(
) )
} }
is DiceThrow.Object -> {
val action = objectRepository.find(
character = diceThrow.character,
item = diceThrow.item,
)
actionThrow(
character = sheet,
action = diceThrow.item,
diceThrow = action?.effect,
title = { getString(R.string.dice_roll_attack_hit_title, it) },
alterations = emptyMap(),
ability = null,
)
}
is DiceThrow.SpellAttack -> { is DiceThrow.SpellAttack -> {
val spell = spellRepository.find( val spell = spellRepository.find(
character = diceThrow.character, character = diceThrow.character,
@ -664,7 +681,7 @@ class DiceThrowUseCase @Inject constructor(
diceThrow: Throw?, diceThrow: Throw?,
title: Context.(action: String) -> String, title: Context.(action: String) -> String,
alterations: Map<Property, List<Alteration.Status>>, alterations: Map<Property, List<Alteration.Status>>,
ability: Property, ability: Property?,
): DiceThrowResult { ): DiceThrowResult {
// retrieve some wording. // retrieve some wording.
val titleString = title(application, action) val titleString = title(application, action)
@ -778,6 +795,17 @@ class DiceThrowUseCase @Inject constructor(
} }
} ?: emptyList() } ?: emptyList()
// check for flat dice bonus (ex: healing potion 2d4 + 2)
val flatBonus = diceThrow?.flat?.takeIf { it > 0 }?.let {
allValue.add(it)
listOf(
ThrowsCardUio.Detail(
title = application.getString(R.string.dice_roll_bonus_detail, action),
result = "$it",
)
)
}?: emptyList()
// build the result. // build the result.
return DiceThrowResult( return DiceThrowResult(
dice = RollDiceUio( dice = RollDiceUio(
@ -812,7 +840,7 @@ class DiceThrowUseCase @Inject constructor(
), ),
result = "${result.value}", result = "${result.value}",
), ),
) + diceAlterationBonus + flatAlterationBonus + relatedStatBonus, ) + diceAlterationBonus + flatAlterationBonus + relatedStatBonus + flatBonus,
) )
) )
} }

View file

@ -40,4 +40,5 @@ sealed class DiceThrow(val character: String) {
class SpellDamage(character: String, val spell: String) : DiceThrow(character) class SpellDamage(character: String, val spell: String) : DiceThrow(character)
class SpellEffect(character: String, val spell: String, val level: Int) : DiceThrow(character) class SpellEffect(character: String, val spell: String, val level: Int) : DiceThrow(character)
class Skill(character: String, val skill: String) : DiceThrow(character) class Skill(character: String, val skill: String) : DiceThrow(character)
class Object(character: String, val item: String) : DiceThrow(character)
} }

View file

@ -0,0 +1,6 @@
package com.pixelized.rplexicon.model
data class ObjectAction(
val name: String,
val effect: Throw?,
)

View file

@ -3,5 +3,6 @@ package com.pixelized.rplexicon.model
class Throw( class Throw(
val amount: Int, val amount: Int,
val faces: Int, val faces: Int,
val flat: Int,
val modifier: List<Property>, val modifier: List<Property>,
) )

View file

@ -14,6 +14,7 @@ object CharacterBinder {
const val CHARACTER = "Feuille de personnage" const val CHARACTER = "Feuille de personnage"
const val ATTACK = "Attaques" const val ATTACK = "Attaques"
const val OBJECT = "Objets"
const val MAGIC = "Magies" const val MAGIC = "Magies"
const val SKILL = "Capacités" const val SKILL = "Capacités"
const val MAGIC_LEXICON = "Lexique magique" const val MAGIC_LEXICON = "Lexique magique"

View file

@ -0,0 +1,40 @@
package com.pixelized.rplexicon.repository.data.character
import com.pixelized.rplexicon.model.ObjectAction
import com.pixelized.rplexicon.repository.GoogleSheetServiceRepository
import com.pixelized.rplexicon.repository.data.CharacterBinder
import com.pixelized.rplexicon.repository.parser.ObjectActionParser
import com.pixelized.rplexicon.utilitary.Update
import com.pixelized.rplexicon.utilitary.exceptions.IncompatibleSheetStructure
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import javax.inject.Inject
import javax.inject.Singleton
@Singleton
class ObjectActionRepository @Inject constructor(
private val googleRepository: GoogleSheetServiceRepository,
private val parser: ObjectActionParser
) {
private val _data = MutableStateFlow<Map<String, List<ObjectAction>>>(emptyMap())
val data: StateFlow<Map<String, List<ObjectAction>>> get() = _data
var lastSuccessFullUpdate: Update = Update.INITIAL
private set
fun find(character: String?): List<ObjectAction> =
_data.value[character] ?: emptyList()
fun find(character: String?, item: String): ObjectAction? =
find(character).firstOrNull { it.name == item }
@Throws(IncompatibleSheetStructure::class, Exception::class)
suspend fun fetchObjectAction() {
googleRepository.fetch { sheet ->
val request = sheet.get(CharacterBinder.ID, CharacterBinder.OBJECT)
val data = parser.parse(data = request.execute())
_data.tryEmit(data)
lastSuccessFullUpdate = Update.currentTime()
}
}
}

View file

@ -0,0 +1,43 @@
package com.pixelized.rplexicon.repository.parser
import com.google.api.services.sheets.v4.model.ValueRange
import com.pixelized.rplexicon.model.ObjectAction
import com.pixelized.rplexicon.utilitary.exceptions.IncompatibleSheetStructure
import javax.inject.Inject
class ObjectActionParser @Inject constructor(
private val throwParser: ThrowParser,
) {
@Throws(IncompatibleSheetStructure::class)
fun parse(data: ValueRange): Map<String, List<ObjectAction>> = parserScope {
val objects = hashMapOf<String, MutableList<ObjectAction>>()
data.forEachRow { index, row ->
when {
index == 0 -> updateStructure(row = row, columns = COLUMNS)
row.isNotEmpty() -> {
val character = row[0]?.toItem()
val name = row.parse(column = NAME)
val effect = throwParser.parse(value = row.parse(column = EFFECT))
if (character != null && name != null && effect != null) {
objects.getOrPut(character) { mutableListOf() }.add(
ObjectAction(
name = name,
effect = effect,
)
)
}
}
}
}
return@parserScope objects
}
companion object {
private const val NAME = "Nom"
private const val EFFECT = "Effet"
private val COLUMNS = listOf(NAME, EFFECT)
}
}

View file

@ -1,12 +1,14 @@
package com.pixelized.rplexicon.repository.parser package com.pixelized.rplexicon.repository.parser
import com.pixelized.rplexicon.repository.parser.roll.DiceParser
import com.pixelized.rplexicon.repository.parser.roll.ModifierParser
import com.pixelized.rplexicon.model.Throw import com.pixelized.rplexicon.model.Throw
import com.pixelized.rplexicon.repository.parser.roll.DiceParser
import com.pixelized.rplexicon.repository.parser.roll.FlatValueParser
import com.pixelized.rplexicon.repository.parser.roll.ModifierParser
import javax.inject.Inject import javax.inject.Inject
class ThrowParser @Inject constructor( class ThrowParser @Inject constructor(
private val diceParser: DiceParser, private val diceParser: DiceParser,
private val flatValueParser: FlatValueParser,
private val modifierParser: ModifierParser, private val modifierParser: ModifierParser,
) { ) {
fun parse(value: String?): Throw? { fun parse(value: String?): Throw? {
@ -14,9 +16,11 @@ class ThrowParser @Inject constructor(
val dice = diceParser.findAll(value = value).firstOrNull() val dice = diceParser.findAll(value = value).firstOrNull()
if (dice != null) { if (dice != null) {
val modifier = modifierParser.findAll(value = value) val modifier = modifierParser.findAll(value = value)
val flat = flatValueParser.parse(value = value)
return Throw( return Throw(
amount = dice.count, amount = dice.count,
faces = dice.faces, faces = dice.faces,
flat = flat,
modifier = modifier, modifier = modifier,
) )
} }

View file

@ -6,7 +6,12 @@ import javax.inject.Inject
class FlatValueParser @Inject constructor() { class FlatValueParser @Inject constructor() {
companion object { companion object {
private val FLAT_REGEX = Regex("(?<!d|\\d)(-?\\d+)(?!d)") private val FLAT_REGEX = Regex("(?<!d)([-+]?\\s?\\d+)(?!d)")
}
fun parse(value: String): Int {
return FLAT_REGEX.findAll(value)
.sumOf { it.value.replace(" ", "").toIntOrNull() ?: 0 }
} }
fun findAll(title: String, value: String): List<Roll.Bonus> { fun findAll(title: String, value: String): List<Roll.Bonus> {

View file

@ -28,6 +28,7 @@ sealed class FetchErrorUio {
DESCRIPTION, DESCRIPTION,
EQUIPMENT, EQUIPMENT,
INVENTORY, INVENTORY,
OBJECT,
SKILL, SKILL,
SPELL, SPELL,
LEXICON, LEXICON,
@ -53,6 +54,7 @@ fun HandleFetchError(
FetchErrorUio.Structure.Type.DESCRIPTION -> R.string.error_structure_description FetchErrorUio.Structure.Type.DESCRIPTION -> R.string.error_structure_description
FetchErrorUio.Structure.Type.EQUIPMENT -> R.string.error_structure_equipment FetchErrorUio.Structure.Type.EQUIPMENT -> R.string.error_structure_equipment
FetchErrorUio.Structure.Type.INVENTORY -> R.string.error_structure_inventory FetchErrorUio.Structure.Type.INVENTORY -> R.string.error_structure_inventory
FetchErrorUio.Structure.Type.OBJECT -> R.string.error_structure_objects
FetchErrorUio.Structure.Type.SKILL -> R.string.error_structure_skill FetchErrorUio.Structure.Type.SKILL -> R.string.error_structure_skill
FetchErrorUio.Structure.Type.SPELL -> R.string.error_structure_spell FetchErrorUio.Structure.Type.SPELL -> R.string.error_structure_spell
FetchErrorUio.Structure.Type.LEXICON -> R.string.error_structure_lexicon FetchErrorUio.Structure.Type.LEXICON -> R.string.error_structure_lexicon

View file

@ -68,6 +68,9 @@ import com.pixelized.rplexicon.ui.screens.character.composable.character.StatUio
import com.pixelized.rplexicon.ui.screens.character.pages.actions.ActionPage import com.pixelized.rplexicon.ui.screens.character.pages.actions.ActionPage
import com.pixelized.rplexicon.ui.screens.character.pages.actions.ActionPagePreview import com.pixelized.rplexicon.ui.screens.character.pages.actions.ActionPagePreview
import com.pixelized.rplexicon.ui.screens.character.pages.actions.AttacksViewModel import com.pixelized.rplexicon.ui.screens.character.pages.actions.AttacksViewModel
import com.pixelized.rplexicon.ui.screens.character.pages.actions.HeaderViewModel
import com.pixelized.rplexicon.ui.screens.character.pages.actions.ObjectsViewModel
import com.pixelized.rplexicon.ui.screens.character.pages.actions.SkillsViewModel
import com.pixelized.rplexicon.ui.screens.character.pages.actions.SpellsViewModel import com.pixelized.rplexicon.ui.screens.character.pages.actions.SpellsViewModel
import com.pixelized.rplexicon.ui.screens.character.pages.alteration.AlterationPage import com.pixelized.rplexicon.ui.screens.character.pages.alteration.AlterationPage
import com.pixelized.rplexicon.ui.screens.character.pages.alteration.AlterationPagePreview import com.pixelized.rplexicon.ui.screens.character.pages.alteration.AlterationPagePreview
@ -93,8 +96,11 @@ fun CharacterSheetScreen(
viewModel: CharacterSheetViewModel = hiltViewModel(), viewModel: CharacterSheetViewModel = hiltViewModel(),
inventoryViewModel: InventoryViewModel = hiltViewModel(), inventoryViewModel: InventoryViewModel = hiltViewModel(),
proficiencyViewModel: ProficiencyViewModel = hiltViewModel(), proficiencyViewModel: ProficiencyViewModel = hiltViewModel(),
headerViewModel: HeaderViewModel = hiltViewModel(),
attacksViewModel: AttacksViewModel = hiltViewModel(), attacksViewModel: AttacksViewModel = hiltViewModel(),
objectsViewModel: ObjectsViewModel = hiltViewModel(),
spellsViewModel: SpellsViewModel = hiltViewModel(), spellsViewModel: SpellsViewModel = hiltViewModel(),
skillViewModel: SkillsViewModel = hiltViewModel(),
alterationsViewModel: AlterationViewModel = hiltViewModel(), alterationsViewModel: AlterationViewModel = hiltViewModel(),
) { ) {
val snack = LocalSnack.current val snack = LocalSnack.current
@ -153,8 +159,11 @@ fun CharacterSheetScreen(
actions = { actions = {
ActionPage( ActionPage(
sheetState = sheetState, sheetState = sheetState,
headerViewModel = headerViewModel,
attacksViewModel = attacksViewModel, attacksViewModel = attacksViewModel,
objectsViewModel = objectsViewModel,
spellsViewModel = spellsViewModel, spellsViewModel = spellsViewModel,
skillViewModel = skillViewModel,
) )
}, },
alterations = { alterations = {

View file

@ -13,6 +13,7 @@ import com.pixelized.rplexicon.repository.data.character.CharacterSheetRepositor
import com.pixelized.rplexicon.repository.data.character.DescriptionRepository import com.pixelized.rplexicon.repository.data.character.DescriptionRepository
import com.pixelized.rplexicon.repository.data.character.EquipmentRepository import com.pixelized.rplexicon.repository.data.character.EquipmentRepository
import com.pixelized.rplexicon.repository.data.character.InventoryRepository import com.pixelized.rplexicon.repository.data.character.InventoryRepository
import com.pixelized.rplexicon.repository.data.character.ObjectActionRepository
import com.pixelized.rplexicon.repository.data.character.SkillRepository import com.pixelized.rplexicon.repository.data.character.SkillRepository
import com.pixelized.rplexicon.repository.data.character.SpellRepository import com.pixelized.rplexicon.repository.data.character.SpellRepository
import com.pixelized.rplexicon.ui.composable.error.FetchErrorUio import com.pixelized.rplexicon.ui.composable.error.FetchErrorUio
@ -35,6 +36,7 @@ class CharacterSheetViewModel @Inject constructor(
private val inventoryRepository: InventoryRepository, private val inventoryRepository: InventoryRepository,
private val equipmentRepository: EquipmentRepository, private val equipmentRepository: EquipmentRepository,
private val actionRepository: ActionRepository, private val actionRepository: ActionRepository,
private val objectRepository: ObjectActionRepository,
private val spellRepository: SpellRepository, private val spellRepository: SpellRepository,
private val skillRepository: SkillRepository, private val skillRepository: SkillRepository,
private val firebaseRepository: FirebaseRepository, private val firebaseRepository: FirebaseRepository,
@ -124,6 +126,16 @@ class CharacterSheetViewModel @Inject constructor(
} }
} }
} }
val objects = async {
if (force || objectRepository.lastSuccessFullUpdate.shouldUpdate()) {
try {
objectRepository.fetchObjectAction()
} catch (exception: Exception) {
Log.e(TAG, exception.message, exception)
_error.emit(FetchErrorUio.Structure(type = Type.OBJECT))
}
}
}
val spells = async { val spells = async {
if (force || spellRepository.lastSuccessFullUpdate.shouldUpdate()) { if (force || spellRepository.lastSuccessFullUpdate.shouldUpdate()) {
try { try {
@ -144,7 +156,7 @@ class CharacterSheetViewModel @Inject constructor(
} }
} }
} }
awaitAll(description, alterations, inventory, equipment, actions, spells, skill) awaitAll(description, alterations, inventory, equipment, actions, objects, spells, skill)
_isLoading.value = false _isLoading.value = false
} }

View file

@ -0,0 +1,114 @@
package com.pixelized.rplexicon.ui.screens.character.composable.actions
import android.content.res.Configuration.UI_MODE_NIGHT_NO
import android.content.res.Configuration.UI_MODE_NIGHT_YES
import androidx.compose.foundation.BorderStroke
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.ExperimentalLayoutApi
import androidx.compose.foundation.layout.FlowRow
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.MaterialTheme
import androidx.compose.material3.OutlinedButton
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.Stable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.font.FontStyle
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import com.pixelized.rplexicon.R
import com.pixelized.rplexicon.ui.theme.LexiconTheme
@Stable
data class ObjectItemUio(
val name: String,
val original: String?,
)
@OptIn(ExperimentalLayoutApi::class)
@Composable
fun ObjectItem(
modifier: Modifier = Modifier,
padding: PaddingValues = PaddingValues(top = 4.dp, bottom = 4.dp, start = 16.dp, end = 16.dp),
item: ObjectItemUio,
onObject: (String) -> Unit,
onUse: (String) -> Unit,
) {
Row(
modifier = Modifier
.clickable { onObject(item.name) }
.padding(paddingValues = padding)
.then(other = modifier),
horizontalArrangement = Arrangement.spacedBy(space = 12.dp),
verticalAlignment = Alignment.CenterVertically,
) {
Column(
modifier = Modifier.weight(weight = 1f)
) {
FlowRow(
horizontalArrangement = Arrangement.spacedBy(space = 4.dp),
) {
Text(
modifier = Modifier.alignByBaseline(),
style = MaterialTheme.typography.bodyMedium,
fontWeight = FontWeight.Bold,
overflow = TextOverflow.Ellipsis,
maxLines = 1,
text = item.name,
)
item.original?.let {
Text(
modifier = Modifier.alignByBaseline(),
style = MaterialTheme.typography.labelSmall,
fontWeight = FontWeight.Light,
fontStyle = FontStyle.Italic,
overflow = TextOverflow.Ellipsis,
maxLines = 1,
text = it,
)
}
}
}
OutlinedButton(
border = BorderStroke(
width = 1.dp,
color = MaterialTheme.colorScheme.primary,
),
onClick = { onUse(item.name) },
) {
Text(
text = stringResource(id = R.string.character_sheet_action_spell_cast),
)
}
}
}
@Composable
@Preview(uiMode = UI_MODE_NIGHT_NO)
@Preview(uiMode = UI_MODE_NIGHT_YES)
private fun ObjectItemPreview() {
LexiconTheme {
Surface {
ObjectItem(
modifier = Modifier.fillMaxWidth(),
item = ObjectItemUio(
name = "Potion de guérison",
original = "Healing potion",
),
onObject = { },
onUse = { },
)
}
}
}

View file

@ -0,0 +1,20 @@
package com.pixelized.rplexicon.ui.screens.character.composable.preview
import androidx.compose.runtime.Composable
import androidx.compose.runtime.Stable
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import com.pixelized.rplexicon.ui.screens.character.composable.actions.ObjectItemUio
@Composable
@Stable
fun rememberObjectListStatePreview() = remember {
mutableStateOf(
listOf(
ObjectItemUio(
name = "Potion de guérison",
original = "Healing potion",
),
)
)
}

View file

@ -17,13 +17,10 @@ import androidx.compose.material.ModalBottomSheetState
import androidx.compose.material3.Surface import androidx.compose.material3.Surface
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.State import androidx.compose.runtime.State
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.compose.ui.window.Dialog
import androidx.compose.ui.window.DialogProperties
import androidx.hilt.navigation.compose.hiltViewModel import androidx.hilt.navigation.compose.hiltViewModel
import com.pixelized.rplexicon.LocalRollOverlay import com.pixelized.rplexicon.LocalRollOverlay
import com.pixelized.rplexicon.R import com.pixelized.rplexicon.R
@ -35,6 +32,8 @@ import com.pixelized.rplexicon.ui.navigation.screens.navigateToSpellDetail
import com.pixelized.rplexicon.ui.screens.character.composable.actions.Attack import com.pixelized.rplexicon.ui.screens.character.composable.actions.Attack
import com.pixelized.rplexicon.ui.screens.character.composable.actions.AttackUio import com.pixelized.rplexicon.ui.screens.character.composable.actions.AttackUio
import com.pixelized.rplexicon.ui.screens.character.composable.actions.GenericHeader import com.pixelized.rplexicon.ui.screens.character.composable.actions.GenericHeader
import com.pixelized.rplexicon.ui.screens.character.composable.actions.ObjectItem
import com.pixelized.rplexicon.ui.screens.character.composable.actions.ObjectItemUio
import com.pixelized.rplexicon.ui.screens.character.composable.actions.SkillItem import com.pixelized.rplexicon.ui.screens.character.composable.actions.SkillItem
import com.pixelized.rplexicon.ui.screens.character.composable.actions.SkillItemUio import com.pixelized.rplexicon.ui.screens.character.composable.actions.SkillItemUio
import com.pixelized.rplexicon.ui.screens.character.composable.actions.Spell import com.pixelized.rplexicon.ui.screens.character.composable.actions.Spell
@ -46,6 +45,7 @@ import com.pixelized.rplexicon.ui.screens.character.composable.character.Charact
import com.pixelized.rplexicon.ui.screens.character.composable.character.CharacterSheetHeaderUio import com.pixelized.rplexicon.ui.screens.character.composable.character.CharacterSheetHeaderUio
import com.pixelized.rplexicon.ui.screens.character.composable.preview.rememberAttackListStatePreview import com.pixelized.rplexicon.ui.screens.character.composable.preview.rememberAttackListStatePreview
import com.pixelized.rplexicon.ui.screens.character.composable.preview.rememberCharacterHeaderStatePreview import com.pixelized.rplexicon.ui.screens.character.composable.preview.rememberCharacterHeaderStatePreview
import com.pixelized.rplexicon.ui.screens.character.composable.preview.rememberObjectListStatePreview
import com.pixelized.rplexicon.ui.screens.character.composable.preview.rememberSpellListStatePreview import com.pixelized.rplexicon.ui.screens.character.composable.preview.rememberSpellListStatePreview
import com.pixelized.rplexicon.ui.theme.LexiconTheme import com.pixelized.rplexicon.ui.theme.LexiconTheme
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
@ -56,6 +56,7 @@ fun ActionPage(
sheetState: ModalBottomSheetState, sheetState: ModalBottomSheetState,
headerViewModel: HeaderViewModel = hiltViewModel(), headerViewModel: HeaderViewModel = hiltViewModel(),
attacksViewModel: AttacksViewModel = hiltViewModel(), attacksViewModel: AttacksViewModel = hiltViewModel(),
objectsViewModel: ObjectsViewModel = hiltViewModel(),
spellsViewModel: SpellsViewModel = hiltViewModel(), spellsViewModel: SpellsViewModel = hiltViewModel(),
skillViewModel: SkillsViewModel = hiltViewModel(), skillViewModel: SkillsViewModel = hiltViewModel(),
) { ) {
@ -67,6 +68,7 @@ fun ActionPage(
modifier = Modifier.fillMaxSize(), modifier = Modifier.fillMaxSize(),
header = headerViewModel.header, header = headerViewModel.header,
attacks = attacksViewModel.attacks, attacks = attacksViewModel.attacks,
objects = objectsViewModel.objects,
tokens = skillViewModel.skills, tokens = skillViewModel.skills,
spells = spellsViewModel.spells, spells = spellsViewModel.spells,
onHitPoint = headerViewModel::toggleHitPointDialog, onHitPoint = headerViewModel::toggleHitPointDialog,
@ -82,6 +84,15 @@ fun ActionPage(
overlay.showOverlay() overlay.showOverlay()
} }
}, },
onObject = {
},
onUseObject = { id ->
objectsViewModel.onUse(id)?.let {
overlay.prepareRoll(diceThrow = it)
overlay.showOverlay()
}
},
onSkillCount = { onSkillCount = {
skillViewModel.showSkillEditDialog(item = it) skillViewModel.showSkillEditDialog(item = it)
}, },
@ -151,11 +162,14 @@ fun ActionsPageContent(
lazyListState: LazyListState = rememberLazyListState(), lazyListState: LazyListState = rememberLazyListState(),
header: State<CharacterSheetHeaderUio?>, header: State<CharacterSheetHeaderUio?>,
attacks: State<List<AttackUio>>, attacks: State<List<AttackUio>>,
objects: State<List<ObjectItemUio>>,
tokens: State<List<SkillItemUio>>, tokens: State<List<SkillItemUio>>,
spells: State<List<Pair<SpellHeaderUio, List<SpellUio>>>>, spells: State<List<Pair<SpellHeaderUio, List<SpellUio>>>>,
onHitPoint: () -> Unit, onHitPoint: () -> Unit,
onAttackHit: (id: String) -> Unit, onAttackHit: (id: String) -> Unit,
onAttackDamage: (id: String) -> Unit, onAttackDamage: (id: String) -> Unit,
onObject: (id: String) -> Unit,
onUseObject: (id: String) -> Unit,
onSkillThrow: (SkillItemUio) -> Unit, onSkillThrow: (SkillItemUio) -> Unit,
onSkillCount: (SkillItemUio) -> Unit, onSkillCount: (SkillItemUio) -> Unit,
onSkillInfo: (SkillItemUio) -> Unit, onSkillInfo: (SkillItemUio) -> Unit,
@ -195,6 +209,24 @@ fun ActionsPageContent(
} }
} }
if (objects.value.isNotEmpty()) {
stickyHeader {
GenericHeader(
label = R.string.character_sheet_title_objects,
)
}
items(items = objects.value) {
ObjectItem(
item = it,
onObject = onObject,
onUse = onUseObject,
)
}
items(count = 1) {
Spacer(modifier = Modifier.height(height = 16.dp))
}
}
if (tokens.value.isNotEmpty()) { if (tokens.value.isNotEmpty()) {
stickyHeader { stickyHeader {
GenericHeader( GenericHeader(
@ -248,11 +280,14 @@ fun ActionPagePreview() {
modifier = Modifier.fillMaxSize(), modifier = Modifier.fillMaxSize(),
header = rememberCharacterHeaderStatePreview(), header = rememberCharacterHeaderStatePreview(),
attacks = rememberAttackListStatePreview(), attacks = rememberAttackListStatePreview(),
objects = rememberObjectListStatePreview(),
tokens = rememberTokenListStatePreview(), tokens = rememberTokenListStatePreview(),
spells = rememberSpellListStatePreview(), spells = rememberSpellListStatePreview(),
onHitPoint = { }, onHitPoint = { },
onAttackHit = { }, onAttackHit = { },
onAttackDamage = { }, onAttackDamage = { },
onObject = { },
onUseObject = { },
onSkillCount = { }, onSkillCount = { },
onSkillThrow = { }, onSkillThrow = { },
onSkillInfo = { }, onSkillInfo = { },

View file

@ -0,0 +1,60 @@
package com.pixelized.rplexicon.ui.screens.character.pages.actions
import androidx.compose.runtime.State
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.repository.data.character.DescriptionRepository
import com.pixelized.rplexicon.repository.data.character.ObjectActionRepository
import com.pixelized.rplexicon.ui.navigation.screens.characterSheetArgument
import com.pixelized.rplexicon.ui.screens.character.composable.actions.ObjectItemUio
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import javax.inject.Inject
@HiltViewModel
class ObjectsViewModel @Inject constructor(
private val objectsRepository: ObjectActionRepository,
private val descriptionRepository: DescriptionRepository,
savedStateHandle: SavedStateHandle,
) : ViewModel() {
val character = savedStateHandle.characterSheetArgument.name
private val _objects = mutableStateOf<List<ObjectItemUio>>(emptyList())
val objects: State<List<ObjectItemUio>> get() = _objects
init {
viewModelScope.launch(Dispatchers.IO) {
objectsRepository.data.collect { objects ->
val data = objects[character]?.map {
ObjectItemUio(
name = it.name,
original = descriptionRepository.find(name = it.name)?.original
)
} ?: emptyList()
withContext(Dispatchers.Main) {
_objects.value = data
}
}
}
}
fun onObject(name: String) {
}
fun onUse(name: String): DiceThrow? {
val item = objectsRepository.find(character = character, item = name)
return item?.let {
DiceThrow.Object(
character = character,
item = it.name,
)
}
}
}

View file

@ -78,6 +78,8 @@ class AlterationFactory @Inject constructor(
listOf(Property.PHYSICAL_RANGE_ATTACK) + (action?.hit?.modifier ?: emptyList()) listOf(Property.PHYSICAL_RANGE_ATTACK) + (action?.hit?.modifier ?: emptyList())
} }
is DiceThrow.Object -> emptyList()
is DiceThrow.PhysicalRangeDamage -> { is DiceThrow.PhysicalRangeDamage -> {
val action = actionRepository.find(diceThrow.character, action = diceThrow.weapon) val action = actionRepository.find(diceThrow.character, action = diceThrow.weapon)
listOf(Property.PHYSICAL_RANGE_DAMAGE) + (action?.damage?.modifier ?: emptyList()) listOf(Property.PHYSICAL_RANGE_DAMAGE) + (action?.damage?.modifier ?: emptyList())

View file

@ -3,6 +3,7 @@ package com.pixelized.rplexicon.ui.screens.rolls.factory
import com.pixelized.rplexicon.R import com.pixelized.rplexicon.R
import com.pixelized.rplexicon.model.DiceThrow import com.pixelized.rplexicon.model.DiceThrow
import com.pixelized.rplexicon.repository.data.character.ActionRepository import com.pixelized.rplexicon.repository.data.character.ActionRepository
import com.pixelized.rplexicon.repository.data.character.ObjectActionRepository
import com.pixelized.rplexicon.repository.data.character.SkillRepository import com.pixelized.rplexicon.repository.data.character.SkillRepository
import com.pixelized.rplexicon.repository.data.character.SpellRepository import com.pixelized.rplexicon.repository.data.character.SpellRepository
import com.pixelized.rplexicon.ui.screens.rolls.composable.RollDiceUio import com.pixelized.rplexicon.ui.screens.rolls.composable.RollDiceUio
@ -11,6 +12,7 @@ import javax.inject.Inject
class DiceFactory @Inject constructor( class DiceFactory @Inject constructor(
private val actionRepository: ActionRepository, private val actionRepository: ActionRepository,
private val objectActionRepository: ObjectActionRepository,
private val skillRepository: SkillRepository, private val skillRepository: SkillRepository,
private val spellRepository: SpellRepository, private val spellRepository: SpellRepository,
) { ) {
@ -50,6 +52,13 @@ class DiceFactory @Inject constructor(
RollDiceUio(icon = it) RollDiceUio(icon = it)
} }
is DiceThrow.Object -> objectActionRepository.find(
character = diceThrow.character,
item = diceThrow.item,
)?.effect?.faces?.icon?.let {
RollDiceUio(icon = it)
}
else -> RollDiceUio(icon = R.drawable.ic_d20_24) else -> RollDiceUio(icon = R.drawable.ic_d20_24)
} }
} }

View file

@ -11,6 +11,7 @@
<string name="error_structure_description">La structure de la feuille de description semble avoir changé et n\'est plus compatible avec cette application.</string> <string name="error_structure_description">La structure de la feuille de description semble avoir changé et n\'est plus compatible avec cette application.</string>
<string name="error_structure_equipment">La structure de la feuille d\'équipement semble avoir changé et n\'est plus compatible avec cette application.</string> <string name="error_structure_equipment">La structure de la feuille d\'équipement semble avoir changé et n\'est plus compatible avec cette application.</string>
<string name="error_structure_inventory">La structure de la feuille d\'inventaire semble avoir changé et n\'est plus compatible avec cette application.</string> <string name="error_structure_inventory">La structure de la feuille d\'inventaire semble avoir changé et n\'est plus compatible avec cette application.</string>
<string name="error_structure_objects">La structure de la feuille d\'actions (object) semble avoir changé et n\'est plus compatible avec cette application.</string>
<string name="error_structure_skill">La structure de la feuille des talents semble avoir changé et n\'est plus compatible avec cette application.</string> <string name="error_structure_skill">La structure de la feuille des talents semble avoir changé et n\'est plus compatible avec cette application.</string>
<string name="error_structure_spell">La structure de la feuille des sorts semble avoir changé et n\'est plus compatible avec cette application.</string> <string name="error_structure_spell">La structure de la feuille des sorts semble avoir changé et n\'est plus compatible avec cette application.</string>
<string name="error_structure_lexicon">La structure du lexique semble avoir changé et n\'est plus compatible avec cette application.</string> <string name="error_structure_lexicon">La structure du lexique semble avoir changé et n\'est plus compatible avec cette application.</string>
@ -99,6 +100,7 @@
<string name="character_sheet_title_proficiencies">Maîtrises</string> <string name="character_sheet_title_proficiencies">Maîtrises</string>
<string name="character_sheet_title_skills">Capacités</string> <string name="character_sheet_title_skills">Capacités</string>
<string name="character_sheet_title_attacks">Attaques</string> <string name="character_sheet_title_attacks">Attaques</string>
<string name="character_sheet_title_objects">Objets</string>
<string name="character_sheet_title_inventory">Inventaire</string> <string name="character_sheet_title_inventory">Inventaire</string>
<string name="character_sheet_title_equipment">Equipement</string> <string name="character_sheet_title_equipment">Equipement</string>
<string name="character_sheet_stat_strength">Force</string> <string name="character_sheet_stat_strength">Force</string>

View file

@ -11,6 +11,7 @@
<string name="error_structure_description">The description sheet structure appears to have changed and is no longer compatible with this application</string> <string name="error_structure_description">The description sheet structure appears to have changed and is no longer compatible with this application</string>
<string name="error_structure_equipment">The equipment sheet structure appears to have changed and is no longer compatible with this application</string> <string name="error_structure_equipment">The equipment sheet structure appears to have changed and is no longer compatible with this application</string>
<string name="error_structure_inventory">The inventory sheet structure appears to have changed and is no longer compatible with this application</string> <string name="error_structure_inventory">The inventory sheet structure appears to have changed and is no longer compatible with this application</string>
<string name="error_structure_objects">The objects sheet structure appears to have changed and is no longer compatible with this application</string>
<string name="error_structure_skill">The skill sheet structure appears to have changed and is no longer compatible with this application</string> <string name="error_structure_skill">The skill sheet structure appears to have changed and is no longer compatible with this application</string>
<string name="error_structure_spell">The spell sheet structure appears to have changed and is no longer compatible with this application</string> <string name="error_structure_spell">The spell sheet structure appears to have changed and is no longer compatible with this application</string>
<string name="error_structure_lexicon">The lexicon sheet structure appears to have changed and is no longer compatible with this application</string> <string name="error_structure_lexicon">The lexicon sheet structure appears to have changed and is no longer compatible with this application</string>
@ -98,6 +99,7 @@
<string name="character_sheet_title_saving_throws">Saving Throws</string> <string name="character_sheet_title_saving_throws">Saving Throws</string>
<string name="character_sheet_title_proficiencies">Proficiencies</string> <string name="character_sheet_title_proficiencies">Proficiencies</string>
<string name="character_sheet_title_attacks">Attacks</string> <string name="character_sheet_title_attacks">Attacks</string>
<string name="character_sheet_title_objects">Objects</string>
<string name="character_sheet_title_skills">Skills</string> <string name="character_sheet_title_skills">Skills</string>
<string name="character_sheet_title_inventory">Inventory</string> <string name="character_sheet_title_inventory">Inventory</string>
<string name="character_sheet_title_equipment">Equipment</string> <string name="character_sheet_title_equipment">Equipment</string>