From e01aa0893c7cdbb01dc13dcddd9de098e75b7798 Mon Sep 17 00:00:00 2001 From: Thomas Andres Gomez Date: Fri, 29 Sep 2023 15:36:04 +0200 Subject: [PATCH] Alteration rework. --- .../pixelized/rplexicon/LauncherViewModel.kt | 15 +- .../rplexicon/business/DiceThrowUseCase.kt | 939 ++++++++++++++++++ .../pixelized/rplexicon/model/Alteration.kt | 3 + .../rplexicon/model/CharacterSheet.kt | 1 + .../pixelized/rplexicon/model/DiceThrow.kt | 42 + .../com/pixelized/rplexicon/model/Property.kt | 8 + .../repository/data/ActionRepository.kt | 10 +- .../repository/data/AlterationRepository.kt | 59 +- .../data/CharacterSheetRepository.kt | 2 + .../repository/data/SpellRepository.kt | 10 +- .../repository/parser/CharacterSheetParser.kt | 3 + .../parser/alteration/AlterationParser.kt | 88 +- .../alteration/AssignedAlterationParser.kt | 47 + .../parser/alteration/StatusParser.kt | 43 - .../screens/character/CharacterSheetScreen.kt | 3 +- .../character/CharacterSheetViewModel.kt | 40 +- .../factory/CharacterSheetUioFactory.kt | 7 +- .../character/pages/actions/ActionsPage.kt | 24 +- .../pages/actions/AttackActionViewModel.kt | 195 +--- .../pages/actions/SpellsActionViewModel.kt | 181 +--- .../pages/proficiency/ProficiencyPage.kt | 41 +- .../pages/proficiency/ProficiencyViewModel.kt | 424 +------- .../rplexicon/ui/screens/rolls/RollOverlay.kt | 93 +- .../ui/screens/rolls/RollOverlayViewModel.kt | 103 +- .../rolls/composable/AlterationDetail.kt | 115 +++ .../rolls/composable/RollAlteration.kt | 37 +- .../ui/screens/rolls/composable/RollDice.kt | 49 +- .../rolls/factory/AlterationFactory.kt | 98 ++ .../ui/screens/rolls/factory/DiceFactory.kt | 46 + .../ui/screens/spell/SpellDetailViewModel.kt | 2 +- .../rplexicon/utilitary/extentions/IntEx.kt | 2 +- .../extentions/local/AlterationEx.kt | 41 +- .../utilitary/extentions/local/PropertyEx.kt | 50 - .../utilitary/extentions/local/ThrowEx.kt | 20 - app/src/main/res/values-fr/strings.xml | 8 +- app/src/main/res/values/strings.xml | 16 +- 36 files changed, 1777 insertions(+), 1088 deletions(-) create mode 100644 app/src/main/java/com/pixelized/rplexicon/business/DiceThrowUseCase.kt create mode 100644 app/src/main/java/com/pixelized/rplexicon/model/DiceThrow.kt create mode 100644 app/src/main/java/com/pixelized/rplexicon/repository/parser/alteration/AssignedAlterationParser.kt delete mode 100644 app/src/main/java/com/pixelized/rplexicon/repository/parser/alteration/StatusParser.kt create mode 100644 app/src/main/java/com/pixelized/rplexicon/ui/screens/rolls/composable/AlterationDetail.kt create mode 100644 app/src/main/java/com/pixelized/rplexicon/ui/screens/rolls/factory/AlterationFactory.kt create mode 100644 app/src/main/java/com/pixelized/rplexicon/ui/screens/rolls/factory/DiceFactory.kt delete mode 100644 app/src/main/java/com/pixelized/rplexicon/utilitary/extentions/local/PropertyEx.kt delete mode 100644 app/src/main/java/com/pixelized/rplexicon/utilitary/extentions/local/ThrowEx.kt diff --git a/app/src/main/java/com/pixelized/rplexicon/LauncherViewModel.kt b/app/src/main/java/com/pixelized/rplexicon/LauncherViewModel.kt index 0034710..e9dc555 100644 --- a/app/src/main/java/com/pixelized/rplexicon/LauncherViewModel.kt +++ b/app/src/main/java/com/pixelized/rplexicon/LauncherViewModel.kt @@ -67,22 +67,15 @@ class LauncherViewModel @Inject constructor( _error.tryEmit("CharacterSheet fail to update") } } + awaitAll(lexicon, location, quest, characterSheet) + val alteration = async { try { - alterationRepository.fetchAlterationSheet() + alterationRepository.fetchAlterationSheet(sheets = characterSheetRepository.sheets) } catch (exception: Exception) { _error.tryEmit("Alteration lexicon fail to update") } } - val status = async { - try { - alterationRepository.fetchStatusSheet() - } catch (exception: Exception) { - _error.tryEmit("Alteration status fail to update") - } - } - awaitAll(lexicon, location, quest, alteration, status, characterSheet) - val action = async { try { actionRepository.fetchActions() @@ -97,7 +90,7 @@ class LauncherViewModel @Inject constructor( _error.tryEmit("Spell fail to update") } } - awaitAll(action, spell) + awaitAll(alteration, action, spell) isLoading = false } diff --git a/app/src/main/java/com/pixelized/rplexicon/business/DiceThrowUseCase.kt b/app/src/main/java/com/pixelized/rplexicon/business/DiceThrowUseCase.kt new file mode 100644 index 0000000..5ce4603 --- /dev/null +++ b/app/src/main/java/com/pixelized/rplexicon/business/DiceThrowUseCase.kt @@ -0,0 +1,939 @@ +package com.pixelized.rplexicon.business + +import android.app.Application +import android.content.Context +import com.pixelized.rplexicon.R +import com.pixelized.rplexicon.model.Alteration +import com.pixelized.rplexicon.model.AssignedSpell +import com.pixelized.rplexicon.model.CharacterSheet +import com.pixelized.rplexicon.model.DiceThrow +import com.pixelized.rplexicon.model.Property +import com.pixelized.rplexicon.model.Throw +import com.pixelized.rplexicon.repository.data.ActionRepository +import com.pixelized.rplexicon.repository.data.AlterationRepository +import com.pixelized.rplexicon.repository.data.CharacterSheetRepository +import com.pixelized.rplexicon.repository.data.SpellRepository +import com.pixelized.rplexicon.ui.screens.rolls.composable.RollDiceUio +import com.pixelized.rplexicon.ui.screens.rolls.composable.ThrowsCardUio +import com.pixelized.rplexicon.utilitary.extentions.icon +import com.pixelized.rplexicon.utilitary.extentions.local.advantage +import com.pixelized.rplexicon.utilitary.extentions.local.disadvantage +import com.pixelized.rplexicon.utilitary.extentions.local.fail +import com.pixelized.rplexicon.utilitary.extentions.local.isBrutalCritical +import com.pixelized.rplexicon.utilitary.extentions.local.isCritical +import com.pixelized.rplexicon.utilitary.extentions.local.isSavageAttacks +import com.pixelized.rplexicon.utilitary.extentions.local.sum +import com.pixelized.rplexicon.utilitary.extentions.local.toStatus +import com.pixelized.rplexicon.utilitary.extentions.modifier +import com.pixelized.rplexicon.utilitary.extentions.toLabel +import javax.inject.Inject +import kotlin.math.max +import kotlin.math.min + +data class DiceThrowResult( + val dice: RollDiceUio, + val card: ThrowsCardUio, +) + +@Suppress("KotlinConstantConditions") +class DiceThrowUseCase @Inject constructor( + private val application: Application, + private val characterSheetRepository: CharacterSheetRepository, + private val actionRepository: ActionRepository, + private val spellRepository: SpellRepository, + private val alterationRepository: AlterationRepository, +) { + fun roll(diceThrow: DiceThrow, alterationId: List): DiceThrowResult? { + val sheet = characterSheetRepository.find(name = diceThrow.character) + val alterations = alterationRepository.getAlterations(character = diceThrow.character) + .filter { alterationId.contains(it.name) } + .toStatus() + + return if (sheet != null) { + when (diceThrow) { + is DiceThrow.Initiative -> abilityThrow( + character = sheet, + alterations = alterations, + abilityLabel = { getString(R.string.character_sheet_title_initiative) }, + relatedLabel = { getString(R.string.character_sheet_stat_dexterity) }, + ability = Property.INITIATIVE, + relatedStat = Property.DEXTERITY, + ) + + is DiceThrow.Strength -> abilityThrow( + character = sheet, + alterations = alterations, + abilityLabel = { getString(R.string.character_sheet_stat_strength) }, + relatedLabel = { getString(R.string.character_sheet_stat_strength) }, + ability = Property.STRENGTH_THROW, + relatedStat = Property.STRENGTH, + ) + + is DiceThrow.Dexterity -> abilityThrow( + character = sheet, + alterations = alterations, + abilityLabel = { getString(R.string.character_sheet_stat_dexterity) }, + relatedLabel = { getString(R.string.character_sheet_stat_dexterity) }, + ability = Property.DEXTERITY_THROW, + relatedStat = Property.DEXTERITY, + ) + + is DiceThrow.Constitution -> abilityThrow( + character = sheet, + alterations = alterations, + abilityLabel = { getString(R.string.character_sheet_stat_constitution) }, + relatedLabel = { getString(R.string.character_sheet_stat_constitution) }, + ability = Property.CONSTITUTION_THROW, + relatedStat = Property.CONSTITUTION, + ) + + is DiceThrow.Intelligence -> abilityThrow( + character = sheet, + alterations = alterations, + abilityLabel = { getString(R.string.character_sheet_stat_intelligence) }, + relatedLabel = { getString(R.string.character_sheet_stat_intelligence) }, + ability = Property.INTELLIGENCE_THROW, + relatedStat = Property.INTELLIGENCE, + ) + + is DiceThrow.Wisdom -> abilityThrow( + character = sheet, + alterations = alterations, + abilityLabel = { getString(R.string.character_sheet_stat_wisdom) }, + relatedLabel = { getString(R.string.character_sheet_stat_wisdom) }, + ability = Property.WISDOM_THROW, + relatedStat = Property.WISDOM + ) + + is DiceThrow.Charisma -> abilityThrow( + character = sheet, + alterations = alterations, + abilityLabel = { getString(R.string.character_sheet_stat_charisma) }, + relatedLabel = { getString(R.string.character_sheet_stat_charisma) }, + ability = Property.CHARISMA_THROW, + relatedStat = Property.CHARISMA + ) + + is DiceThrow.StrengthSavingThrow -> savingThrow( + character = sheet, + alterations = alterations, + abilityLabel = { getString(R.string.character_sheet_stat_strength) }, + relatedLabel = { getString(R.string.character_sheet_stat_strength) }, + ability = Property.STRENGTH_SAVING_THROW, + relatedStat = Property.STRENGTH, + ) + + is DiceThrow.DexteritySavingThrow -> savingThrow( + character = sheet, + alterations = alterations, + abilityLabel = { getString(R.string.character_sheet_stat_dexterity) }, + relatedLabel = { getString(R.string.character_sheet_stat_dexterity) }, + ability = Property.DEXTERITY_SAVING_THROW, + relatedStat = Property.DEXTERITY, + ) + + is DiceThrow.ConstitutionSavingThrow -> savingThrow( + character = sheet, + alterations = alterations, + abilityLabel = { getString(R.string.character_sheet_stat_constitution) }, + relatedLabel = { getString(R.string.character_sheet_stat_constitution) }, + ability = Property.CONSTITUTION_SAVING_THROW, + relatedStat = Property.CONSTITUTION, + ) + + is DiceThrow.IntelligenceSavingThrow -> savingThrow( + character = sheet, + alterations = alterations, + abilityLabel = { getString(R.string.character_sheet_stat_intelligence) }, + relatedLabel = { getString(R.string.character_sheet_stat_intelligence) }, + ability = Property.INTELLIGENCE_SAVING_THROW, + relatedStat = Property.INTELLIGENCE, + ) + + is DiceThrow.WisdomSavingThrow -> savingThrow( + character = sheet, + alterations = alterations, + abilityLabel = { getString(R.string.character_sheet_stat_wisdom) }, + relatedLabel = { getString(R.string.character_sheet_stat_wisdom) }, + ability = Property.WISDOM_SAVING_THROW, + relatedStat = Property.WISDOM + ) + + is DiceThrow.CharismaSavingThrow -> savingThrow( + character = sheet, + alterations = alterations, + abilityLabel = { getString(R.string.character_sheet_stat_charisma) }, + relatedLabel = { getString(R.string.character_sheet_stat_charisma) }, + ability = Property.CHARISMA_SAVING_THROW, + relatedStat = Property.CHARISMA + ) + + is DiceThrow.Acrobatics -> abilityThrow( + character = sheet, + alterations = alterations, + abilityLabel = { getString(R.string.character_sheet_proficiency_acrobatics) }, + relatedLabel = { getString(R.string.character_sheet_stat_dexterity) }, + ability = Property.ACROBATICS, + relatedStat = Property.DEXTERITY, + ) + + is DiceThrow.AnimalHandling -> abilityThrow( + character = sheet, + alterations = alterations, + abilityLabel = { getString(R.string.character_sheet_proficiency_animal_handling) }, + relatedLabel = { getString(R.string.character_sheet_stat_wisdom) }, + ability = Property.ANIMAL_HANDLING, + relatedStat = Property.WISDOM, + ) + + is DiceThrow.Arcana -> abilityThrow( + character = sheet, + alterations = alterations, + abilityLabel = { getString(R.string.character_sheet_proficiency_arcana) }, + relatedLabel = { getString(R.string.character_sheet_stat_intelligence) }, + ability = Property.ARCANA, + relatedStat = Property.INTELLIGENCE, + ) + + is DiceThrow.Athletics -> abilityThrow( + character = sheet, + alterations = alterations, + abilityLabel = { getString(R.string.character_sheet_proficiency_athletics) }, + relatedLabel = { getString(R.string.character_sheet_stat_strength) }, + ability = Property.ATHLETICS, + relatedStat = Property.STRENGTH, + ) + + is DiceThrow.Deception -> abilityThrow( + character = sheet, + alterations = alterations, + abilityLabel = { getString(R.string.character_sheet_proficiency_deception) }, + relatedLabel = { getString(R.string.character_sheet_stat_charisma) }, + ability = Property.DECEPTION, + relatedStat = Property.CHARISMA, + ) + + is DiceThrow.History -> abilityThrow( + character = sheet, + alterations = alterations, + abilityLabel = { getString(R.string.character_sheet_proficiency_history) }, + relatedLabel = { getString(R.string.character_sheet_stat_intelligence) }, + ability = Property.HISTORY, + relatedStat = Property.INTELLIGENCE, + ) + + is DiceThrow.Insight -> abilityThrow( + character = sheet, + alterations = alterations, + abilityLabel = { getString(R.string.character_sheet_proficiency_insight) }, + relatedLabel = { getString(R.string.character_sheet_stat_wisdom) }, + ability = Property.INSIGHT, + relatedStat = Property.WISDOM, + ) + + is DiceThrow.Intimidation -> abilityThrow( + character = sheet, + alterations = alterations, + abilityLabel = { getString(R.string.character_sheet_proficiency_intimidation) }, + relatedLabel = { getString(R.string.character_sheet_stat_charisma) }, + ability = Property.INTIMIDATION, + relatedStat = Property.CHARISMA, + ) + + is DiceThrow.Investigation -> abilityThrow( + character = sheet, + alterations = alterations, + abilityLabel = { getString(R.string.character_sheet_proficiency_investigation) }, + relatedLabel = { getString(R.string.character_sheet_stat_intelligence) }, + ability = Property.INVESTIGATION, + relatedStat = Property.INTELLIGENCE, + ) + + is DiceThrow.Medicine -> abilityThrow( + character = sheet, + alterations = alterations, + abilityLabel = { getString(R.string.character_sheet_proficiency_medicine) }, + relatedLabel = { getString(R.string.character_sheet_stat_wisdom) }, + ability = Property.MEDICINE, + relatedStat = Property.WISDOM, + ) + + is DiceThrow.Nature -> abilityThrow( + character = sheet, + alterations = alterations, + abilityLabel = { getString(R.string.character_sheet_proficiency_nature) }, + relatedLabel = { getString(R.string.character_sheet_stat_intelligence) }, + ability = Property.NATURE, + relatedStat = Property.INTELLIGENCE, + ) + + is DiceThrow.Perception -> abilityThrow( + character = sheet, + alterations = alterations, + abilityLabel = { getString(R.string.character_sheet_proficiency_perception) }, + relatedLabel = { getString(R.string.character_sheet_stat_wisdom) }, + ability = Property.PERSUASION, + relatedStat = Property.WISDOM, + ) + + is DiceThrow.Performance -> abilityThrow( + character = sheet, + alterations = alterations, + abilityLabel = { getString(R.string.character_sheet_proficiency_performance) }, + relatedLabel = { getString(R.string.character_sheet_stat_charisma) }, + ability = Property.PERFORMANCE, + relatedStat = Property.CHARISMA, + ) + + is DiceThrow.Persuasion -> abilityThrow( + character = sheet, + alterations = alterations, + abilityLabel = { getString(R.string.character_sheet_proficiency_persuasion) }, + relatedLabel = { getString(R.string.character_sheet_stat_charisma) }, + ability = Property.PERSUASION, + relatedStat = Property.CHARISMA, + ) + + is DiceThrow.Religion -> abilityThrow( + character = sheet, + alterations = alterations, + abilityLabel = { getString(R.string.character_sheet_proficiency_religion) }, + relatedLabel = { getString(R.string.character_sheet_stat_intelligence) }, + ability = Property.RELIGION, + relatedStat = Property.INTELLIGENCE, + ) + + is DiceThrow.SleightOfHand -> abilityThrow( + character = sheet, + alterations = alterations, + abilityLabel = { getString(R.string.character_sheet_proficiency_sleight_of_hand) }, + relatedLabel = { getString(R.string.character_sheet_stat_dexterity) }, + ability = Property.SLEIGHT_OF_HAND, + relatedStat = Property.DEXTERITY, + ) + + is DiceThrow.Stealth -> abilityThrow( + character = sheet, + alterations = alterations, + abilityLabel = { getString(R.string.character_sheet_proficiency_stealth) }, + relatedLabel = { getString(R.string.character_sheet_stat_dexterity) }, + ability = Property.STEALTH, + relatedStat = Property.DEXTERITY, + ) + + is DiceThrow.Survival -> abilityThrow( + character = sheet, + alterations = alterations, + abilityLabel = { getString(R.string.character_sheet_proficiency_survival) }, + relatedLabel = { getString(R.string.character_sheet_stat_wisdom) }, + ability = Property.SURVIVAL, + relatedStat = Property.WISDOM, + ) + + is DiceThrow.PhysicalMeleeAttack -> { + val action = actionRepository.find( + character = diceThrow.character, + action = diceThrow.weapon, + ) + actionThrow( + character = sheet, + action = diceThrow.weapon, + diceThrow = action?.hit, + title = { getString(R.string.dice_roll_attack_hit_title, it) }, + alterations = alterations, + ability = Property.PHYSICAL_MELEE_ATTACK, + ) + } + + is DiceThrow.PhysicalMeleeDamage -> { + val action = actionRepository.find( + character = diceThrow.character, + action = diceThrow.weapon, + ) + actionThrow( + character = sheet, + action = diceThrow.weapon, + diceThrow = action?.damage, + title = { getString(R.string.dice_roll_attack_hit_title, it) }, + alterations = alterations, + ability = Property.PHYSICAL_MELEE_DAMAGE, + ) + } + + is DiceThrow.PhysicalRangeAttack -> { + val action = actionRepository.find( + character = diceThrow.character, + action = diceThrow.weapon, + ) + actionThrow( + character = sheet, + action = diceThrow.weapon, + diceThrow = action?.hit, + title = { getString(R.string.dice_roll_attack_hit_title, it) }, + alterations = alterations, + ability = Property.PHYSICAL_RANGE_ATTACK, + ) + } + + is DiceThrow.PhysicalRangeDamage -> { + val action = actionRepository.find( + character = diceThrow.character, + action = diceThrow.weapon, + ) + actionThrow( + character = sheet, + action = diceThrow.weapon, + diceThrow = action?.damage, + title = { getString(R.string.dice_roll_attack_hit_title, it) }, + alterations = alterations, + ability = Property.PHYSICAL_RANGE_DAMAGE, + ) + } + + is DiceThrow.SpellAttack -> { + val spell = spellRepository.find( + character = diceThrow.character, + spell = diceThrow.spell, + ) + actionThrow( + character = sheet, + action = diceThrow.spell, + diceThrow = spell?.hit, + title = { getString(R.string.dice_roll_spell_hit_title, it) }, + alterations = alterations, + ability = Property.SPELL_ATTACK, + ) + } + + is DiceThrow.SpellDamage -> { + val spell = spellRepository.find( + character = diceThrow.character, + spell = diceThrow.spell, + ) + actionThrow( + character = sheet, + action = diceThrow.spell, + diceThrow = spell?.effect, + title = { getString(R.string.dice_roll_spell_damage_title, it) }, + alterations = alterations, + ability = Property.SPELL_DAMAGE, + ) + } + + is DiceThrow.SpellEffect -> { + val spell = spellRepository.find( + character = diceThrow.character, + spell = diceThrow.spell, + ) + spellThrow( + character = sheet, + spell = spell, + level = diceThrow.level, + alterations = alterations, + ) + } + } + } else { + null + } + } + + private fun abilityThrow( + character: CharacterSheet, + alterations: Map>, + abilityLabel: Context.() -> String, + relatedLabel: Context.() -> String?, + ability: Property, + relatedStat: Property, + ): DiceThrowResult = rollAbility( + character = character, + alterations = alterations, + abilityTitle = { getString(R.string.dice_roll_check_title, it) }, + abilityLabel = abilityLabel, + relatedTitle = { label -> getString(R.string.dice_roll_bonus_detail, label) }, + relatedLabel = relatedLabel, + ability = ability, + relatedStat = relatedStat, + ) + + private fun savingThrow( + character: CharacterSheet, + alterations: Map>, + abilityLabel: Context.() -> String, + relatedLabel: Context.() -> String?, + ability: Property, + relatedStat: Property, + ): DiceThrowResult = rollAbility( + character = character, + alterations = alterations, + abilityTitle = { getString(R.string.dice_roll_saving_throw_title, it) }, + abilityLabel = abilityLabel, + relatedTitle = { getString(R.string.dice_roll_saving_throw_detail, it) }, + relatedLabel = relatedLabel, + ability = ability, + relatedStat = relatedStat, + ) + + private fun rollAbility( + character: CharacterSheet, + alterations: Map>, + abilityTitle: Context.(label: String) -> String, + abilityLabel: Context.() -> String, + relatedTitle: Context.(label: String) -> String?, + relatedLabel: Context.() -> String?, + ability: Property, + relatedStat: Property, + ): DiceThrowResult { + // retrieve some wording. + val abilityLabelString = abilityLabel(application) + val abilityTitleString = abilityTitle(application, abilityLabelString) + + // create a list destined to contain all the values (rolled + bonus) + val allValue = mutableListOf() + + // check if the roll is affected by some status. + val advantage = alterations[ability].advantage + val disadvantage = alterations[ability].disadvantage + val fail = alterations[ability].fail + + // main roll + val result = roll(advantage = advantage, disadvantage = disadvantage, fail = fail) + allValue.add(result.value) + + // fetch and build a list of dice roll base on alterations. + val diceAlterationBonus = alterations[ability]?.flatMap { status -> + status.dices.map { dice -> + val localRoll = roll( + amount = dice.count, + faces = dice.faces, + advantage = dice.advantage, + disadvantage = dice.disadvantage, + fail = dice.fail + ) + allValue.add(localRoll.value) + ThrowsCardUio.Detail( + title = dice.title, + throws = ThrowsCardUio.Throw( + dice = dice.faces.icon, + advantage = dice.advantage, + disadvantage = dice.disadvantage, + roll = "${dice.count}d${dice.faces}", + result = localRoll.label, + ), + result = "${localRoll.value}", + ) + } + } ?: emptyList() + + // fetch and build a list of flat bonus + val flatAlterationBonus = alterations[ability]?.flatMap { status -> + status.bonus.map { bonus -> + allValue.add(bonus.value) + ThrowsCardUio.Detail( + title = bonus.title, + result = "${bonus.value}", + ) + } + } ?: emptyList() + + // fetch and build the associated characteristic + val relatedStatBonus = when (relatedStat) { + Property.STRENGTH -> (character.strength + alterations[relatedStat].sum).modifier + Property.DEXTERITY -> (character.dexterity + alterations[relatedStat].sum).modifier + Property.CONSTITUTION -> (character.constitution + alterations[relatedStat].sum).modifier + Property.INTELLIGENCE -> (character.intelligence + alterations[relatedStat].sum).modifier + Property.WISDOM -> (character.wisdom + alterations[relatedStat].sum).modifier + Property.CHARISMA -> (character.charisma + alterations[relatedStat].sum).modifier + Property.PROFICIENCY -> character.proficiency + else -> null + }?.let { value -> + val relatedLabelString = relatedLabel(application) + val relatedTitleString = relatedLabelString?.let { relatedTitle(application, it) } + if (relatedTitleString != null) { + allValue.add(value) + listOf( + ThrowsCardUio.Detail( + title = relatedTitleString, + result = "$value", + ) + ) + } else { + null + } + } ?: emptyList() + + // fetch and build the mastery bonus + val mastery = when (ability) { + Property.STRENGTH_SAVING_THROW -> character.strengthSavingThrows + Property.DEXTERITY_SAVING_THROW -> character.dexteritySavingThrows + Property.CONSTITUTION_SAVING_THROW -> character.constitutionSavingThrows + Property.INTELLIGENCE_SAVING_THROW -> character.intelligenceSavingThrows + Property.WISDOM_SAVING_THROW -> character.wisdomSavingThrows + Property.CHARISMA_SAVING_THROW -> character.charismaSavingThrows + Property.ACROBATICS -> character.acrobatics + Property.ANIMAL_HANDLING -> character.animalHandling + Property.ARCANA -> character.arcana + Property.ATHLETICS -> character.athletics + Property.DECEPTION -> character.deception + Property.HISTORY -> character.history + Property.INSIGHT -> character.insight + Property.INTIMIDATION -> character.intimidation + Property.INVESTIGATION -> character.investigation + Property.MEDICINE -> character.medicine + Property.NATURE -> character.nature + Property.PERCEPTION -> character.perception + Property.PERFORMANCE -> character.performance + Property.PERSUASION -> character.persuasion + Property.RELIGION -> character.religion + Property.SLEIGHT_OF_HAND -> character.sleightOfHand + Property.STEALTH -> character.stealth + Property.SURVIVAL -> character.survival + else -> null + }?.let { multiplier -> + val mastery = character.proficiency * multiplier + allValue.add(mastery) + listOf( + ThrowsCardUio.Detail( + title = application.getString( + when (multiplier) { + 2 -> R.string.dice_roll_expertise_bonus + else -> R.string.dice_roll_proficiency_bonus + } + ), + result = "$mastery", + ), + ) + } ?: emptyList() + + // build the result. + return DiceThrowResult( + dice = RollDiceUio( + isCriticalSuccess = result.value == 20, + isCriticalFailure = result.value == 1, + result = "${result.value}", + ), + card = ThrowsCardUio( + title = abilityTitleString.uppercase(), + highlight = abilityLabelString.uppercase(), + dice = R.drawable.ic_d20_24, + roll = allValue.toLabel(), + result = "${ + when (result.value) { + 20 -> 20 + 1 -> 1 + else -> allValue.sum() + } + }", + isCriticalSuccess = result.value == 20, + isCriticalFailure = result.value == 1, + details = listOf( + ThrowsCardUio.Detail( + title = abilityTitleString, + throws = ThrowsCardUio.Throw( + dice = R.drawable.ic_d20_24, + advantage = advantage, + disadvantage = disadvantage, + roll = "1d20", + result = result.label, + ), + result = "${result.value}", + ), + ) + diceAlterationBonus + flatAlterationBonus + relatedStatBonus + mastery, + ) + ) + } + + private fun actionThrow( + character: CharacterSheet, + action: String, + diceThrow: Throw?, + title: Context.(action: String) -> String, + alterations: Map>, + ability: Property, + ): DiceThrowResult { + // retrieve some wording. + val titleString = title(application, action) + + val canMakeCriticalRoll = + ability == Property.PHYSICAL_MELEE_ATTACK || ability == Property.PHYSICAL_RANGE_ATTACK || ability == Property.SPELL_ATTACK + + var criticalDiceMultiplier = 2 + if (ability == Property.PHYSICAL_MELEE_DAMAGE) { + if (alterations.isSavageAttacks) criticalDiceMultiplier++ + if (alterations.isBrutalCritical) criticalDiceMultiplier++ + } + + // create a list destined to contain all the values (rolled + bonus) + val allValue = mutableListOf() + + // check if the roll is affected by some status. + val advantage = alterations[ability].advantage + val disadvantage = alterations[ability].disadvantage + val fail = alterations[ability].fail + + // main roll + val result = roll( + amount = diceThrow?.amount ?: 1, + faces = diceThrow?.faces ?: 20, + advantage = advantage, + disadvantage = disadvantage, + fail = fail, + critical = if (alterations.isCritical) criticalDiceMultiplier else 1, + ) + allValue.add(result.value) + + // fetch and build a list of dice roll base on alterations. + val diceAlterationBonus = alterations[ability]?.flatMap { status -> + status.dices.map { dice -> + val localRoll = roll( + amount = dice.count, + faces = dice.faces, + advantage = dice.advantage, + disadvantage = dice.disadvantage, + fail = dice.fail + ) + allValue.add(localRoll.value) + ThrowsCardUio.Detail( + title = dice.title, + throws = ThrowsCardUio.Throw( + dice = dice.faces.icon, + advantage = dice.advantage, + disadvantage = dice.disadvantage, + roll = "${dice.count}d${dice.faces}", + result = localRoll.label, + ), + result = "${localRoll.value}", + ) + } + } ?: emptyList() + + // fetch and build a list of flat bonus + val flatAlterationBonus = alterations[ability]?.flatMap { status -> + status.bonus.map { bonus -> + allValue.add(bonus.value) + ThrowsCardUio.Detail( + title = bonus.title, + result = "${bonus.value}", + ) + } + } ?: emptyList() + + // fetch and build the associated characteristic + val relatedStatBonus = diceThrow?.modifier?.mapNotNull { property -> + when (property) { + Property.STRENGTH -> (character.strength + alterations[property].sum).modifier + Property.DEXTERITY -> (character.dexterity + alterations[property].sum).modifier + Property.CONSTITUTION -> (character.constitution + alterations[property].sum).modifier + Property.INTELLIGENCE -> (character.intelligence + alterations[property].sum).modifier + Property.WISDOM -> (character.wisdom + alterations[property].sum).modifier + Property.CHARISMA -> (character.charisma + alterations[property].sum).modifier + Property.PROFICIENCY -> character.proficiency + else -> null + }?.let { value -> + val titleLabel = if (property == Property.PROFICIENCY) { + application.getString(R.string.dice_roll_mastery_proficiency, action) + } else { + val label = when (property) { + Property.STRENGTH -> application.getString(R.string.character_sheet_stat_strength) + Property.DEXTERITY -> application.getString(R.string.character_sheet_stat_dexterity) + Property.CONSTITUTION -> application.getString(R.string.character_sheet_stat_constitution) + Property.INTELLIGENCE -> application.getString(R.string.character_sheet_stat_intelligence) + Property.WISDOM -> application.getString(R.string.character_sheet_stat_wisdom) + Property.CHARISMA -> application.getString(R.string.character_sheet_stat_charisma) + else -> "" + } + application.getString(R.string.dice_roll_bonus_detail, label) + } + allValue.add(value) + ThrowsCardUio.Detail( + title = titleLabel, + result = "$value", + ) + } + } ?: emptyList() + + // build the result. + return DiceThrowResult( + dice = RollDiceUio( + icon = diceThrow?.faces?.icon ?: R.drawable.ic_d20_24, + isCriticalSuccess = canMakeCriticalRoll && result.value == 20, + isCriticalFailure = canMakeCriticalRoll && result.value == 1, + result = "${result.value}", + ), + card = ThrowsCardUio( + title = titleString.uppercase(), + highlight = action, + dice = diceThrow?.faces?.icon ?: R.drawable.ic_d20_24, + isCriticalSuccess = canMakeCriticalRoll && result.value == 20, + isCriticalFailure = canMakeCriticalRoll && result.value == 1, + roll = allValue.toLabel(), + result = "${ + when { + canMakeCriticalRoll && result.value == 20 -> 20 + canMakeCriticalRoll && result.value == 1 -> 1 + else -> allValue.sum() + } + }", + details = listOf( + ThrowsCardUio.Detail( + title = action, + throws = ThrowsCardUio.Throw( + dice = diceThrow?.faces?.icon ?: R.drawable.ic_d20_24, + advantage = advantage, + disadvantage = disadvantage, + roll = "${diceThrow?.amount}d${diceThrow?.faces}", + result = result.label, + ), + result = "${result.value}", + ), + ) + diceAlterationBonus + flatAlterationBonus + relatedStatBonus, + ) + ) + } + + private fun spellThrow( + character: CharacterSheet, + spell: AssignedSpell?, + level: Int, + alterations: Map>, + ): DiceThrowResult { + // retrieve some wording. + val spellName = spell?.spell?.name ?: "" + val titleString = application.getString(R.string.dice_roll_spell_cast, spellName) + + // create a list destined to contain all the values (rolled + bonus) + val allValue = mutableListOf() + + // main roll + val result = roll( + amount = spell?.effect?.amount ?: 1, + faces = spell?.effect?.faces ?: 4, + ) + allValue.add(result.value) + + // fetch and build a list of additionnal level effect. + val levelBonus = if (spell?.level != null) { + ((spell.spell.level + 1)..level).map { + val localRoll = roll( + amount = spell.level.amount, + faces = spell.level.faces, + ) + allValue.add(localRoll.value) + ThrowsCardUio.Detail( + title = application.getString(R.string.spell_level_chooser_label, "$it"), + throws = ThrowsCardUio.Throw( + dice = spell.level.faces.icon, + roll = "${spell.level.amount}d${spell.level.faces}", + result = localRoll.label, + ), + result = "${localRoll.value}", + ) + } + } else { + emptyList() + } + + // fetch and build the associated characteristic + val relatedStatBonus = spell?.effect?.modifier?.mapNotNull { property -> + when (property) { + Property.STRENGTH -> (character.strength + alterations[property].sum).modifier + Property.DEXTERITY -> (character.dexterity + alterations[property].sum).modifier + Property.CONSTITUTION -> (character.constitution + alterations[property].sum).modifier + Property.INTELLIGENCE -> (character.intelligence + alterations[property].sum).modifier + Property.WISDOM -> (character.wisdom + alterations[property].sum).modifier + Property.CHARISMA -> (character.charisma + alterations[property].sum).modifier + Property.PROFICIENCY -> character.proficiency + else -> null + }?.let { value -> + val titleLabel = if (property == Property.PROFICIENCY) { + application.getString(R.string.dice_roll_mastery_proficiency, spell) + } else { + val label = when (property) { + Property.STRENGTH -> application.getString(R.string.character_sheet_stat_strength) + Property.DEXTERITY -> application.getString(R.string.character_sheet_stat_dexterity) + Property.CONSTITUTION -> application.getString(R.string.character_sheet_stat_constitution) + Property.INTELLIGENCE -> application.getString(R.string.character_sheet_stat_intelligence) + Property.WISDOM -> application.getString(R.string.character_sheet_stat_wisdom) + Property.CHARISMA -> application.getString(R.string.character_sheet_stat_charisma) + else -> "" + } + application.getString(R.string.dice_roll_bonus_detail, label) + } + allValue.add(value) + ThrowsCardUio.Detail( + title = titleLabel, + result = "$value", + ) + } + } ?: emptyList() + + // build the result. + return DiceThrowResult( + dice = RollDiceUio( + icon = (spell?.effect?.faces ?: 4).icon, + result = "${result.value}", + ), + card = ThrowsCardUio( + title = titleString.uppercase(), + highlight = spellName, + dice = (spell?.effect?.faces ?: 4).icon, + roll = allValue.toLabel(), + result = "${allValue.sum()}", + details = listOf( + ThrowsCardUio.Detail( + title = spellName, + throws = ThrowsCardUio.Throw( + dice = (spell?.effect?.faces ?: 4).icon, + roll = "${spell?.effect?.amount ?: 1}d${spell?.effect?.faces ?: 4}", + result = result.label, + ), + result = "${result.value}", + ), + ) + levelBonus + relatedStatBonus, + ) + ) + } + + private fun roll( + amount: Int = 1, + faces: Int = 20, + advantage: Boolean = false, + disadvantage: Boolean = false, + fail: Boolean = false, + critical: Int = 1, + ): DiceRollResult { + return when { + advantage && !disadvantage -> { + val roll = List(amount * critical) { random(faces, fail) to random(faces, fail) } + DiceRollResult( + label = roll.joinToString(" + ") { "${it.first}~${it.second}" }, + value = roll.sumOf { max(it.first, it.second) }, + ) + } + + disadvantage && !advantage -> { + val roll = List(amount * critical) { random(faces, fail) to random(faces, fail) } + DiceRollResult( + label = roll.joinToString(" + ") { "${it.first}~${it.second}" }, + value = roll.sumOf { min(it.first, it.second) }, + ) + } + + else -> { + val roll = List(amount * critical) { random(faces, fail) } + DiceRollResult( + label = roll.toLabel(), + value = roll.sum(), + ) + } + } + } + + private fun random(faces: Int, fail: Boolean): Int = + if (fail) 1 else (Math.random() * faces + 1).toInt() + + private data class DiceRollResult( + val label: String, + val value: Int, + ) +} \ No newline at end of file diff --git a/app/src/main/java/com/pixelized/rplexicon/model/Alteration.kt b/app/src/main/java/com/pixelized/rplexicon/model/Alteration.kt index 4d5fd89..3734b9a 100644 --- a/app/src/main/java/com/pixelized/rplexicon/model/Alteration.kt +++ b/app/src/main/java/com/pixelized/rplexicon/model/Alteration.kt @@ -2,6 +2,9 @@ package com.pixelized.rplexicon.model data class Alteration( val name: String, + val target: String?, + val active: Boolean = false, + val description: String, val status: Map, ) { data class Status( diff --git a/app/src/main/java/com/pixelized/rplexicon/model/CharacterSheet.kt b/app/src/main/java/com/pixelized/rplexicon/model/CharacterSheet.kt index e8464be..0fbbf39 100644 --- a/app/src/main/java/com/pixelized/rplexicon/model/CharacterSheet.kt +++ b/app/src/main/java/com/pixelized/rplexicon/model/CharacterSheet.kt @@ -2,6 +2,7 @@ package com.pixelized.rplexicon.model data class CharacterSheet( val name: String, + val race: String?, val proficiency: Int, // Bonus de maîtrise val level: Int, // Niveau val characterClass: String, // Classe diff --git a/app/src/main/java/com/pixelized/rplexicon/model/DiceThrow.kt b/app/src/main/java/com/pixelized/rplexicon/model/DiceThrow.kt new file mode 100644 index 0000000..56e9a44 --- /dev/null +++ b/app/src/main/java/com/pixelized/rplexicon/model/DiceThrow.kt @@ -0,0 +1,42 @@ +package com.pixelized.rplexicon.model + +sealed class DiceThrow(val character: String) { + class Initiative(character: String) : DiceThrow(character) + class Strength(character: String) : DiceThrow(character) + class Dexterity(character: String) : DiceThrow(character) + class Constitution(character: String) : DiceThrow(character) + class Intelligence(character: String) : DiceThrow(character) + class Wisdom(character: String) : DiceThrow(character) + class Charisma(character: String) : DiceThrow(character) + class StrengthSavingThrow(character: String) : DiceThrow(character) + class DexteritySavingThrow(character: String) : DiceThrow(character) + class ConstitutionSavingThrow(character: String) : DiceThrow(character) + class IntelligenceSavingThrow(character: String) : DiceThrow(character) + class WisdomSavingThrow(character: String) : DiceThrow(character) + class CharismaSavingThrow(character: String) : DiceThrow(character) + class Acrobatics(character: String) : DiceThrow(character) + class AnimalHandling(character: String) : DiceThrow(character) + class Arcana(character: String) : DiceThrow(character) + class Athletics(character: String) : DiceThrow(character) + class Deception(character: String) : DiceThrow(character) + class History(character: String) : DiceThrow(character) + class Insight(character: String) : DiceThrow(character) + class Intimidation(character: String) : DiceThrow(character) + class Investigation(character: String) : DiceThrow(character) + class Medicine(character: String) : DiceThrow(character) + class Nature(character: String) : DiceThrow(character) + class Perception(character: String) : DiceThrow(character) + class Performance(character: String) : DiceThrow(character) + class Persuasion(character: String) : DiceThrow(character) + class Religion(character: String) : DiceThrow(character) + class SleightOfHand(character: String) : DiceThrow(character) + class Stealth(character: String) : DiceThrow(character) + class Survival(character: String) : DiceThrow(character) + class PhysicalMeleeAttack(character: String, val weapon: String) : DiceThrow(character) + class PhysicalMeleeDamage(character: String, val weapon: String) : DiceThrow(character) + class PhysicalRangeAttack(character: String, val weapon: String) : DiceThrow(character) + class PhysicalRangeDamage(character: String, val weapon: String) : DiceThrow(character) + class SpellAttack(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) +} \ No newline at end of file diff --git a/app/src/main/java/com/pixelized/rplexicon/model/Property.kt b/app/src/main/java/com/pixelized/rplexicon/model/Property.kt index 82f87ba..e469856 100644 --- a/app/src/main/java/com/pixelized/rplexicon/model/Property.kt +++ b/app/src/main/java/com/pixelized/rplexicon/model/Property.kt @@ -11,6 +11,12 @@ enum class Property(val key: String) { INTELLIGENCE("Intelligence"), WISDOM("Sagesse"), CHARISMA("Charisme"), + STRENGTH_THROW("Jet de force"), + DEXTERITY_THROW("Jet de dextérité"), + CONSTITUTION_THROW("Jet de constitution"), + INTELLIGENCE_THROW("Jet d'Intelligence"), + WISDOM_THROW("Jet de sagesse"), + CHARISMA_THROW("Jet de charisme"), STRENGTH_SAVING_THROW("Jet de sauvegarde: Force"), DEXTERITY_SAVING_THROW("Jet de sauvegarde: Dextérité"), CONSTITUTION_SAVING_THROW("Jet de sauvegarde: Constitution"), @@ -39,4 +45,6 @@ enum class Property(val key: String) { PHYSICAL_MELEE_DAMAGE("Dommage physique corps à corps"), PHYSICAL_RANGE_ATTACK("Attaque physique à distance"), PHYSICAL_RANGE_DAMAGE("Dommage physique à distance"), + SPELL_ATTACK("Attaque sortilège"), + SPELL_DAMAGE("Dommage sortilège"), } \ No newline at end of file diff --git a/app/src/main/java/com/pixelized/rplexicon/repository/data/ActionRepository.kt b/app/src/main/java/com/pixelized/rplexicon/repository/data/ActionRepository.kt index 2ccbcfb..cf95649 100644 --- a/app/src/main/java/com/pixelized/rplexicon/repository/data/ActionRepository.kt +++ b/app/src/main/java/com/pixelized/rplexicon/repository/data/ActionRepository.kt @@ -1,8 +1,8 @@ package com.pixelized.rplexicon.repository.data -import com.pixelized.rplexicon.repository.parser.AttackParser import com.pixelized.rplexicon.model.Attack import com.pixelized.rplexicon.repository.GoogleSheetServiceRepository +import com.pixelized.rplexicon.repository.parser.AttackParser import com.pixelized.rplexicon.utilitary.Update import com.pixelized.rplexicon.utilitary.exceptions.IncompatibleSheetStructure import kotlinx.coroutines.flow.MutableStateFlow @@ -22,8 +22,12 @@ class ActionRepository @Inject constructor( var lastSuccessFullUpdate: Update = Update.INITIAL private set - fun find(name: String?): List? { - return name?.let { _data.value[it] } + fun find(character: String?): List? { + return character?.let { _data.value[it] } + } + + fun find(character: String?, action: String): Attack? { + return find(character)?.firstOrNull { it.title == action } } @Throws(IncompatibleSheetStructure::class, Exception::class) diff --git a/app/src/main/java/com/pixelized/rplexicon/repository/data/AlterationRepository.kt b/app/src/main/java/com/pixelized/rplexicon/repository/data/AlterationRepository.kt index 4438746..a832735 100644 --- a/app/src/main/java/com/pixelized/rplexicon/repository/data/AlterationRepository.kt +++ b/app/src/main/java/com/pixelized/rplexicon/repository/data/AlterationRepository.kt @@ -1,12 +1,10 @@ package com.pixelized.rplexicon.repository.data -import com.pixelized.rplexicon.repository.parser.alteration.AlterationParser -import com.pixelized.rplexicon.repository.parser.alteration.CounterParser -import com.pixelized.rplexicon.repository.parser.alteration.StatusParser import com.pixelized.rplexicon.model.Alteration -import com.pixelized.rplexicon.model.Counter +import com.pixelized.rplexicon.model.CharacterSheet import com.pixelized.rplexicon.model.Property import com.pixelized.rplexicon.repository.GoogleSheetServiceRepository +import com.pixelized.rplexicon.repository.parser.alteration.AlterationParser import com.pixelized.rplexicon.utilitary.Update import com.pixelized.rplexicon.utilitary.exceptions.IncompatibleSheetStructure import kotlinx.coroutines.flow.MutableStateFlow @@ -18,57 +16,30 @@ import javax.inject.Singleton class AlterationRepository @Inject constructor( private val googleRepository: GoogleSheetServiceRepository, private val alterationParser: AlterationParser, - private val statusParser: StatusParser, - private val counterParser: CounterParser, ) { - private val _alterationsLexicon = MutableStateFlow>(emptyList()) - val alterationsLexicon: StateFlow> get() = _alterationsLexicon + private val _assignedAlterations = MutableStateFlow>>(emptyMap()) + val assignedAlterations: StateFlow>> get() = _assignedAlterations - private val _assignedAlterations = MutableStateFlow>>(emptyMap()) - val assignedAlterations: StateFlow>> get() = _assignedAlterations - - private val _counter = MutableStateFlow>>(emptyMap()) - val counter: StateFlow>> get() = _counter - - var lastAlterationSuccessFullUpdate: Update = Update.INITIAL + var lastSuccessFullUpdate: Update = Update.INITIAL private set - var lastStatusSuccessFullUpdate: Update = Update.INITIAL - private set - - fun getAlterations(character: String): List { - return assignedAlterations.value[character]?.mapNotNull { alterationName -> - alterationsLexicon.value.firstOrNull { it.name == alterationName } - } ?: emptyList() + fun getAlterations(character: String) : List { + return _assignedAlterations.value[character] ?: emptyList() } - fun getStatus(character: String, property: Property): List { - return getAlterations(character).mapNotNull { it.status[property] } - } - - fun getCounter(name: String?): List? { - return name?.let { counter.value[it] } - } - - @Throws(IncompatibleSheetStructure::class, Exception::class) - suspend fun fetchAlterationSheet() { - googleRepository.fetch { sheet -> - val request = sheet.get(Sheet.Character.ID, Sheet.Character.ALTERATION) - val data = alterationParser.parse(value = request.execute()) - _alterationsLexicon.emit(data) - lastAlterationSuccessFullUpdate = Update.currentTime() + fun getAlterations(character: String, vararg properties: Property): List { + return getAlterations(character = character).filter { + it.status.keys.any { key -> properties.contains(key) } } } @Throws(IncompatibleSheetStructure::class, Exception::class) - suspend fun fetchStatusSheet() { + suspend fun fetchAlterationSheet(sheets: List) { googleRepository.fetch { sheet -> - val request = sheet.get(Sheet.Character.ID, Sheet.Character.STATUS) - val status = statusParser.parse(value = request.execute()) - _assignedAlterations.emit(status) - val counter = counterParser.parse(values = request.execute()) - _counter.emit(counter) - lastStatusSuccessFullUpdate = Update.currentTime() + val request = sheet.get(Sheet.Character.ID, Sheet.Character.ALTERATION) + val data = alterationParser.parse(values = request.execute(), sheets = sheets) + _assignedAlterations.emit(data) + lastSuccessFullUpdate = Update.currentTime() } } } \ No newline at end of file diff --git a/app/src/main/java/com/pixelized/rplexicon/repository/data/CharacterSheetRepository.kt b/app/src/main/java/com/pixelized/rplexicon/repository/data/CharacterSheetRepository.kt index eca4ce0..6935705 100644 --- a/app/src/main/java/com/pixelized/rplexicon/repository/data/CharacterSheetRepository.kt +++ b/app/src/main/java/com/pixelized/rplexicon/repository/data/CharacterSheetRepository.kt @@ -18,6 +18,8 @@ class CharacterSheetRepository @Inject constructor( private val _data = MutableStateFlow>(emptyMap()) val data: StateFlow> get() = _data + val sheets: List get() = data.value.values.toList() + var lastSuccessFullUpdate: Update = Update.INITIAL private set diff --git a/app/src/main/java/com/pixelized/rplexicon/repository/data/SpellRepository.kt b/app/src/main/java/com/pixelized/rplexicon/repository/data/SpellRepository.kt index 0321b2b..32ec909 100644 --- a/app/src/main/java/com/pixelized/rplexicon/repository/data/SpellRepository.kt +++ b/app/src/main/java/com/pixelized/rplexicon/repository/data/SpellRepository.kt @@ -1,10 +1,10 @@ package com.pixelized.rplexicon.repository.data -import com.pixelized.rplexicon.repository.parser.spell.AssignedSpellParser -import com.pixelized.rplexicon.repository.parser.spell.SpellBookParser import com.pixelized.rplexicon.model.AssignedSpell import com.pixelized.rplexicon.model.Spell import com.pixelized.rplexicon.repository.GoogleSheetServiceRepository +import com.pixelized.rplexicon.repository.parser.spell.AssignedSpellParser +import com.pixelized.rplexicon.repository.parser.spell.SpellBookParser import com.pixelized.rplexicon.utilitary.Update import com.pixelized.rplexicon.utilitary.exceptions.IncompatibleSheetStructure import kotlinx.coroutines.async @@ -27,12 +27,12 @@ class SpellRepository @Inject constructor( var lastSuccessFullUpdate: Update = Update.INITIAL private set - fun findAssignedSpells(character: String?): List? { + fun find(character: String?): List? { return character?.let { _spells.value[it] } } - fun findAssignedSpell(character: String?, spell: String): AssignedSpell? { - return character?.let { _spells.value[it]?.find { assigned -> assigned.spell.name == spell } } + fun find(character: String?, spell: String): AssignedSpell? { + return find(character)?.find { assigned -> assigned.spell.name == spell } } @Throws(IncompatibleSheetStructure::class, Exception::class) diff --git a/app/src/main/java/com/pixelized/rplexicon/repository/parser/CharacterSheetParser.kt b/app/src/main/java/com/pixelized/rplexicon/repository/parser/CharacterSheetParser.kt index da6cab0..abc318c 100644 --- a/app/src/main/java/com/pixelized/rplexicon/repository/parser/CharacterSheetParser.kt +++ b/app/src/main/java/com/pixelized/rplexicon/repository/parser/CharacterSheetParser.kt @@ -47,6 +47,7 @@ class CharacterSheetParser @Inject constructor( if (name != null) { CharacterSheet( name = name, + race = item.parseString(RACE), proficiency = item.parseInt(MASTERY) ?: 2, level = item.parseInt(LEVEL) ?: 2, characterClass = item.parseString(CLASS) ?: "", @@ -127,6 +128,7 @@ class CharacterSheetParser @Inject constructor( companion object { private const val NAME = "Nom" + private const val RACE = "Race" private const val LEVEL = "Niveau" private const val CLASS = "Classe" private const val HIT_POINT = "Point de vie" @@ -180,6 +182,7 @@ class CharacterSheetParser @Inject constructor( private val ROWS get() = listOf( NAME, + RACE, LEVEL, CLASS, HIT_POINT, diff --git a/app/src/main/java/com/pixelized/rplexicon/repository/parser/alteration/AlterationParser.kt b/app/src/main/java/com/pixelized/rplexicon/repository/parser/alteration/AlterationParser.kt index 37ddfe7..ae2d546 100644 --- a/app/src/main/java/com/pixelized/rplexicon/repository/parser/alteration/AlterationParser.kt +++ b/app/src/main/java/com/pixelized/rplexicon/repository/parser/alteration/AlterationParser.kt @@ -1,10 +1,11 @@ package com.pixelized.rplexicon.repository.parser.alteration import com.google.api.services.sheets.v4.model.ValueRange +import com.pixelized.rplexicon.model.Alteration +import com.pixelized.rplexicon.model.CharacterSheet +import com.pixelized.rplexicon.model.Property import com.pixelized.rplexicon.repository.parser.roll.DiceParser import com.pixelized.rplexicon.repository.parser.roll.FlatValueParser -import com.pixelized.rplexicon.model.Alteration -import com.pixelized.rplexicon.model.Property import com.pixelized.rplexicon.utilitary.exceptions.IncompatibleSheetStructure import com.pixelized.rplexicon.utilitary.extentions.local.checkSheetStructure import com.pixelized.rplexicon.utilitary.extentions.sheet @@ -13,66 +14,91 @@ import javax.inject.Inject class AlterationParser @Inject constructor( private val diceParser: DiceParser, private val flatParser: FlatValueParser, - private val propertyParser: PropertyParser, ) { @Throws(IncompatibleSheetStructure::class) - fun parse(value: ValueRange): List { - val sheet = value.values.sheet() - lateinit var structure: Map + fun parse(values: ValueRange, sheets: List): Map> { + val sheet = values.values.sheet() - return sheet?.mapIndexedNotNull { index, row -> + val properties = Property.values() + val data = hashMapOf>() + + lateinit var alterationStructure: Map + lateinit var charactersStructure: Map + + fun List<*>.parseString(key: String): String? = + (getOrNull(alterationStructure.getValue(key)) as? String)?.takeIf { it.isNotEmpty() } + + sheet?.forEachIndexed { index, row -> when { index == 0 -> { - structure = row.checkSheetStructure(model = COLUMNS) - null + alterationStructure = row.checkSheetStructure(model = COLUMNS) + charactersStructure = row.checkSheetStructure(model = sheets.map { it.name }) } row is List<*> -> { // Assume that the name is the first column. - val name = row.getOrNull(0) as? String - - if (name != null) { + val alteration = (row.getOrNull(0) as? String)?.let { name -> + val target = row.parseString(TARGET) + val description = row.parseString(DESCRIPTION) Alteration( name = name, - status = COLUMNS - .mapNotNull { column -> - val property = propertyParser.parseProperty(column) - val flat = row.getOrNull(structure.getValue(column)) as? String - if (property != null && !flat.isNullOrEmpty()) { - property to parseAlterationStatus(name, flat) + target = target, + active = false, + description = description ?: "", + status = properties + .mapNotNull { property -> + val column = alterationStructure.getValue(property.key) + val value = row.getOrNull(column) as? String + if (value?.isNotEmpty() == true) { + property to parseAlterationStatus(name, value) } else { null } } .toMap(), ) - } else { - null + } + + if (alteration != null) { + sheets + .filter { // check if the alteration is applicable to the character + alteration.target?.let { target -> + target == ALL || target == it.characterClass || target == it.race || target == it.name + } ?: false + } + .forEach { // check the default alteration state for that character + val column = charactersStructure.getValue(it.name) + val default = (row.getOrNull(column) as? String)?.toBoolean() + val assignedAlteration = alteration.copy( + active = default ?: false + ) + data.getOrPut(it.name) { mutableListOf() }.add(assignedAlteration) + } } } - - else -> null } - } ?: emptyList() + } + + return data } private fun parseAlterationStatus(name: String, value: String): Alteration.Status = when (value) { - "adv" -> Alteration.Status( + ADVANTAGE -> Alteration.Status( name = name, advantage = true, disadvantage = false, fail = false, ) - "dis" -> Alteration.Status( + DISADVANTAGE -> Alteration.Status( name = name, advantage = false, disadvantage = true, fail = false, ) - "fail" -> Alteration.Status( + FAIL -> Alteration.Status( name = name, advantage = false, disadvantage = false, @@ -92,6 +118,14 @@ class AlterationParser @Inject constructor( } companion object { - private val COLUMNS get() = Property.values().map { it.key } + private const val ALL = "Tous" + private const val ADVANTAGE = "adv" + private const val DISADVANTAGE = "dis" + private const val FAIL = "fail" + + private const val TARGET = "Cible" + private const val DESCRIPTION = "Description" + private val COLUMNS + get() = listOf(TARGET, DESCRIPTION) + Property.values().map { it.key } } } \ No newline at end of file diff --git a/app/src/main/java/com/pixelized/rplexicon/repository/parser/alteration/AssignedAlterationParser.kt b/app/src/main/java/com/pixelized/rplexicon/repository/parser/alteration/AssignedAlterationParser.kt new file mode 100644 index 0000000..fa08c54 --- /dev/null +++ b/app/src/main/java/com/pixelized/rplexicon/repository/parser/alteration/AssignedAlterationParser.kt @@ -0,0 +1,47 @@ +package com.pixelized.rplexicon.repository.parser.alteration + +import com.google.api.services.sheets.v4.model.ValueRange +import com.pixelized.rplexicon.utilitary.exceptions.IncompatibleSheetStructure +import com.pixelized.rplexicon.utilitary.extentions.local.checkSheetStructure +import com.pixelized.rplexicon.utilitary.extentions.sheet +import javax.inject.Inject + +class AssignedAlterationParser @Inject constructor() { + + @Throws(IncompatibleSheetStructure::class) + fun parse(value: ValueRange, characters: List): Map> { + val sheet = value.values.sheet() + + val status = hashMapOf>() + lateinit var structure: Map + + sheet?.mapNotNull { it as? List<*> }?.forEachIndexed { index, row -> + when (index) { + 0 -> { + structure = row.checkSheetStructure(model = characters) + } + + else -> { + val alteration = row.getOrNull(0)?.toString() + + if (alteration != null) { + characters.forEach { character -> + val value = row.getOrNull(structure.getValue(character)) + if (value == TRUE) { + status + .getOrPut(character) { mutableListOf() } + .add(alteration) + } + } + } + } + } + } + + return status + } + + companion object { + private const val TRUE = "TRUE" + } +} \ No newline at end of file diff --git a/app/src/main/java/com/pixelized/rplexicon/repository/parser/alteration/StatusParser.kt b/app/src/main/java/com/pixelized/rplexicon/repository/parser/alteration/StatusParser.kt deleted file mode 100644 index 838bad5..0000000 --- a/app/src/main/java/com/pixelized/rplexicon/repository/parser/alteration/StatusParser.kt +++ /dev/null @@ -1,43 +0,0 @@ -package com.pixelized.rplexicon.repository.parser.alteration - -import com.google.api.services.sheets.v4.model.ValueRange -import com.pixelized.rplexicon.utilitary.exceptions.IncompatibleSheetStructure -import com.pixelized.rplexicon.utilitary.extentions.sheet -import javax.inject.Inject - -class StatusParser @Inject constructor() { - - @Throws(IncompatibleSheetStructure::class) - fun parse(value: ValueRange): Map> { - val sheet = value.values.sheet() - lateinit var characters: List - val status = hashMapOf>() - - sheet?.mapNotNull { it as? List<*> }?.forEachIndexed { columnIndex, row -> - when (columnIndex) { - 0 -> characters = row - .subList(fromIndex = 1, toIndex = row.size) - .map { it.toString() } - - else -> { - row.getOrNull(0)?.toString()?.let { alteration -> - row.subList(fromIndex = 1, toIndex = row.size) - .forEachIndexed { rowIndex, value -> - if (value == TRUE) { - status - .getOrPut(characters[rowIndex]) { mutableListOf() } - .add(alteration) - } - } - } - } - } - } - - return status - } - - companion object { - private const val TRUE = "TRUE" - } -} \ No newline at end of file diff --git a/app/src/main/java/com/pixelized/rplexicon/ui/screens/character/CharacterSheetScreen.kt b/app/src/main/java/com/pixelized/rplexicon/ui/screens/character/CharacterSheetScreen.kt index 6bed760..1142833 100644 --- a/app/src/main/java/com/pixelized/rplexicon/ui/screens/character/CharacterSheetScreen.kt +++ b/app/src/main/java/com/pixelized/rplexicon/ui/screens/character/CharacterSheetScreen.kt @@ -142,8 +142,7 @@ fun CharacterSheetScreen( spells = spellsViewModel.preparedSpellLevel, onLevel = { spell, level -> scope.launch { sheetState.hide() } - val roll = spellsViewModel.onCastSpell(spell, level) - overlay.prepareRoll(roll = roll) + overlay.prepareRoll(diceThrow = spellsViewModel.onCastSpell(spell, level)) overlay.showOverlay() }, ) diff --git a/app/src/main/java/com/pixelized/rplexicon/ui/screens/character/CharacterSheetViewModel.kt b/app/src/main/java/com/pixelized/rplexicon/ui/screens/character/CharacterSheetViewModel.kt index c7ff06c..8652a7b 100644 --- a/app/src/main/java/com/pixelized/rplexicon/ui/screens/character/CharacterSheetViewModel.kt +++ b/app/src/main/java/com/pixelized/rplexicon/ui/screens/character/CharacterSheetViewModel.kt @@ -13,18 +13,13 @@ import com.pixelized.rplexicon.repository.data.SpellRepository import com.pixelized.rplexicon.ui.navigation.screens.characterSheetArgument import com.pixelized.rplexicon.ui.screens.character.composable.character.CharacterSheetHeaderUio import com.pixelized.rplexicon.ui.screens.character.factory.CharacterSheetHeaderUioFactory -import com.pixelized.rplexicon.utilitary.extentions.local.status +import com.pixelized.rplexicon.utilitary.extentions.local.toActiveStatus import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.async import kotlinx.coroutines.awaitAll import kotlinx.coroutines.coroutineScope -import kotlinx.coroutines.flow.collect import kotlinx.coroutines.flow.combine -import kotlinx.coroutines.flow.flowOn -import kotlinx.coroutines.flow.map -import kotlinx.coroutines.flow.mapNotNull -import kotlinx.coroutines.flow.zip import kotlinx.coroutines.launch import kotlinx.coroutines.withContext import javax.inject.Inject @@ -53,10 +48,11 @@ class CharacterSheetViewModel @Inject constructor( .combine(alterationRepository.assignedAlterations) { sheets, _ -> sheets } .collect { sheets -> _header.value = withContext(Dispatchers.Default) { - val alterations = alterationRepository.getAlterations(character = character) + val alterations = + alterationRepository.getAlterations(character = character) headerFactory.convert( model = sheets.getValue(character), - alterations = alterations.status(), + alterations = alterations.toActiveStatus(), ) } } @@ -69,7 +65,7 @@ class CharacterSheetViewModel @Inject constructor( suspend fun update(force: Boolean) = coroutineScope { _isLoading.value = true - val characterRequest = async { + val characters = async { try { if (force || characterRepository.lastSuccessFullUpdate.shouldUpdate()) { characterRepository.fetchCharacterSheet() @@ -80,10 +76,11 @@ class CharacterSheetViewModel @Inject constructor( Log.e(TAG, exception.message, exception) } } - val alterationRequest = async { + awaitAll(characters) + val alterations = async { try { - if (force || alterationRepository.lastAlterationSuccessFullUpdate.shouldUpdate()) { - alterationRepository.fetchAlterationSheet() + if (force || alterationRepository.lastSuccessFullUpdate.shouldUpdate()) { + alterationRepository.fetchAlterationSheet(sheets = characterRepository.sheets) } else { Unit } @@ -91,20 +88,7 @@ class CharacterSheetViewModel @Inject constructor( Log.e(TAG, exception.message, exception) } } - val statusRequest = async { - try { - if (force || alterationRepository.lastStatusSuccessFullUpdate.shouldUpdate()) { - alterationRepository.fetchStatusSheet() - } else { - Unit - } - } catch (exception: Exception) { - Log.e(TAG, exception.message, exception) - } - } - awaitAll(characterRequest, statusRequest, alterationRequest) - - val actionRequest = async { + val actions = async { try { if (force || actionRepository.lastSuccessFullUpdate.shouldUpdate()) { actionRepository.fetchActions() @@ -115,7 +99,7 @@ class CharacterSheetViewModel @Inject constructor( Log.e(TAG, exception.message, exception) } } - val spellRequest = async { + val spells = async { try { if (force || spellRepository.lastSuccessFullUpdate.shouldUpdate()) { spellRepository.fetchSpells() @@ -126,7 +110,7 @@ class CharacterSheetViewModel @Inject constructor( Log.e(TAG, exception.message, exception) } } - awaitAll(actionRequest, spellRequest) + awaitAll(alterations, actions, spells) _isLoading.value = false } diff --git a/app/src/main/java/com/pixelized/rplexicon/ui/screens/character/factory/CharacterSheetUioFactory.kt b/app/src/main/java/com/pixelized/rplexicon/ui/screens/character/factory/CharacterSheetUioFactory.kt index 198055f..bc459f7 100644 --- a/app/src/main/java/com/pixelized/rplexicon/ui/screens/character/factory/CharacterSheetUioFactory.kt +++ b/app/src/main/java/com/pixelized/rplexicon/ui/screens/character/factory/CharacterSheetUioFactory.kt @@ -1,7 +1,9 @@ package com.pixelized.rplexicon.ui.screens.character.factory import com.pixelized.rplexicon.R +import com.pixelized.rplexicon.model.Alteration import com.pixelized.rplexicon.model.CharacterSheet +import com.pixelized.rplexicon.model.Property import com.pixelized.rplexicon.ui.screens.character.composable.character.LabelPointUio import com.pixelized.rplexicon.ui.screens.character.composable.character.ProficiencyUio import com.pixelized.rplexicon.ui.screens.character.composable.character.StatUio @@ -12,7 +14,10 @@ import javax.inject.Inject class CharacterSheetUioFactory @Inject constructor() { - fun convert(sheet: CharacterSheet): CharacterSheetUio { + fun convert( + sheet: CharacterSheet, + alterations: Map>, // TODO use this param to reflect on the character sheet + ): CharacterSheetUio { return CharacterSheetUio( initiative = LabelPointUio( label = R.string.character_sheet_title_initiative, diff --git a/app/src/main/java/com/pixelized/rplexicon/ui/screens/character/pages/actions/ActionsPage.kt b/app/src/main/java/com/pixelized/rplexicon/ui/screens/character/pages/actions/ActionsPage.kt index 9b98948..c93d79d 100644 --- a/app/src/main/java/com/pixelized/rplexicon/ui/screens/character/pages/actions/ActionsPage.kt +++ b/app/src/main/java/com/pixelized/rplexicon/ui/screens/character/pages/actions/ActionsPage.kt @@ -3,7 +3,6 @@ package com.pixelized.rplexicon.ui.screens.character.pages.actions import android.content.res.Configuration.UI_MODE_NIGHT_NO import android.content.res.Configuration.UI_MODE_NIGHT_YES import androidx.compose.foundation.ExperimentalFoundationApi -import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth @@ -53,14 +52,16 @@ fun ActionPage( attacks = attackViewModel.attacks, spells = spellsViewModel.spells, onAttackHit = { id -> - val roll = attackViewModel.onHitRoll(id) - overlay.prepareRoll(roll = roll) - overlay.showOverlay() + attackViewModel.onHitRoll(id)?.let { + overlay.prepareRoll(diceThrow = it) + overlay.showOverlay() + } }, onAttackDamage = { id -> - val roll = attackViewModel.onDamageRoll(id) - overlay.prepareRoll(roll = roll) - overlay.showOverlay() + attackViewModel.onDamageRoll(id)?.let { + overlay.prepareRoll(diceThrow = it) + overlay.showOverlay() + } }, onSpell = { spell -> screen.navigateToSpellDetail( @@ -69,13 +70,11 @@ fun ActionPage( ) }, onSpellHit = { id -> - val roll = spellsViewModel.onSpellHitRoll(id) - overlay.prepareRoll(roll = roll) + overlay.prepareRoll(diceThrow = spellsViewModel.onSpellHitRoll(id)) overlay.showOverlay() }, onSpellDamage = { id -> - val roll = spellsViewModel.onSpellDamageRoll(id) - overlay.prepareRoll(roll = roll) + overlay.prepareRoll(diceThrow = spellsViewModel.onSpellDamageRoll(id)) overlay.showOverlay() }, onCast = { @@ -83,8 +82,7 @@ fun ActionPage( spellsViewModel.prepareSpellCast(it) scope.launch { sheetState.show() } } else { - val roll = spellsViewModel.onCastSpell(it) - overlay.prepareRoll(roll = roll) + overlay.prepareRoll(diceThrow = spellsViewModel.onCastSpell(it)) overlay.showOverlay() } }, diff --git a/app/src/main/java/com/pixelized/rplexicon/ui/screens/character/pages/actions/AttackActionViewModel.kt b/app/src/main/java/com/pixelized/rplexicon/ui/screens/character/pages/actions/AttackActionViewModel.kt index b44af8a..af85d70 100644 --- a/app/src/main/java/com/pixelized/rplexicon/ui/screens/character/pages/actions/AttackActionViewModel.kt +++ b/app/src/main/java/com/pixelized/rplexicon/ui/screens/character/pages/actions/AttackActionViewModel.kt @@ -6,21 +6,16 @@ import androidx.compose.runtime.mutableStateOf import androidx.lifecycle.AndroidViewModel import androidx.lifecycle.SavedStateHandle import androidx.lifecycle.viewModelScope -import com.pixelized.rplexicon.R import com.pixelized.rplexicon.model.Attack import com.pixelized.rplexicon.model.CharacterSheet -import com.pixelized.rplexicon.model.Property -import com.pixelized.rplexicon.model.Roll -import com.pixelized.rplexicon.model.Throw +import com.pixelized.rplexicon.model.DiceThrow import com.pixelized.rplexicon.repository.data.ActionRepository import com.pixelized.rplexicon.repository.data.AlterationRepository import com.pixelized.rplexicon.repository.data.CharacterSheetRepository import com.pixelized.rplexicon.ui.navigation.screens.characterSheetArgument import com.pixelized.rplexicon.ui.screens.character.composable.actions.AttackUio import com.pixelized.rplexicon.ui.screens.character.factory.AttackUioFactory -import com.pixelized.rplexicon.utilitary.extentions.context -import com.pixelized.rplexicon.utilitary.extentions.local.status -import com.pixelized.rplexicon.utilitary.extentions.modifier +import com.pixelized.rplexicon.utilitary.extentions.local.toActiveStatus import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch @@ -51,7 +46,7 @@ class AttackActionViewModel @Inject constructor( sheets[character]?.map { action -> attackFactory.convert( characterSheet = model, - alterations = alterations.status(), + alterations = alterations.toActiveStatus(), attack = action, ) } ?: emptyList() @@ -61,169 +56,37 @@ class AttackActionViewModel @Inject constructor( } } - fun onHitRoll(id: String): Roll { - val action = actionRepository.find(character)?.firstOrNull { it.title == id } - return actionRoll( - attack = action, - throws = action?.hit, - canUseCriticalDice = false, - ) - } + fun onHitRoll(id: String): DiceThrow? { + val action = actionRepository.find(character = character, action = id) + return when (action?.type) { + Attack.Type.PHYSICAL_MELEE_ATTACK -> DiceThrow.PhysicalMeleeAttack( + character = character, + weapon = id, + ) - fun onDamageRoll(id: String): Roll { - val action = actionRepository.find(character)?.firstOrNull { it.title == id } - return actionRoll( - attack = action, - throws = action?.damage, - canUseCriticalDice = true, - ) - } + Attack.Type.PHYSICAL_RANGE_ATTACK -> DiceThrow.PhysicalRangeAttack( + character = character, + weapon = id, + ) - private fun actionRoll( - attack: Attack?, - throws: Throw?, - canUseCriticalDice: Boolean, - ): Roll { - // build the title - val title = context.getString( - when (attack?.type) { - else -> when (throws === attack?.hit) { - true -> R.string.dice_roll_attack_hit_title - else -> R.string.dice_roll_attack_damage_title - } - }, - attack?.title, - ) - // get the alteration for roll and a given player - val alterations = when (attack?.type) { - Attack.Type.PHYSICAL_MELEE_ATTACK -> { - alterationRepository.getStatus( - character = model.name, - property = when (throws === attack.hit) { - true -> Property.PHYSICAL_MELEE_ATTACK - else -> Property.PHYSICAL_MELEE_DAMAGE - }, - ) - } - - Attack.Type.PHYSICAL_RANGE_ATTACK -> { - alterationRepository.getStatus( - character = model.name, - property = when (throws === attack.hit) { - true -> Property.PHYSICAL_RANGE_ATTACK - else -> Property.PHYSICAL_RANGE_DAMAGE - }, - ) - } - - else -> { - emptyList() - } + else -> null } - // check if any alteration give us advantage or disadvantage - val advantage = alterations.any { it.advantage } - val disadvantage = alterations.any { it.disadvantage } - val fail = alterations.any { it.fail } - // build the roll - return Roll( - character = character, - canUseCriticalDice = canUseCriticalDice, - title = title, - highlight = attack?.title, - dices = listOf( - Roll.Dice( - title = attack?.title, - advantage = advantage, - disadvantage = disadvantage, - fail = fail, - count = throws?.amount ?: 1, - faces = throws?.faces ?: 20, - ) - ) + alterations.map { it.dices }.flatten(), - bonus = (throws?.modifier - ?.mapNotNull { - when (it) { - Property.PROFICIENCY -> { - Roll.Bonus( - title = context.getString(R.string.dice_roll_proficiency_bonus), - value = model.proficiency, - ) - } + } - Property.STRENGTH -> { - val related = - context.getString(R.string.character_sheet_stat_strength) - Roll.Bonus( - title = context.getString( - R.string.dice_roll_bonus_detail, - related - ), - value = model.strength.modifier, - ) - } + fun onDamageRoll(id: String): DiceThrow? { + val action = actionRepository.find(character = character, action = id) + return when (action?.type) { + Attack.Type.PHYSICAL_MELEE_ATTACK -> DiceThrow.PhysicalMeleeDamage( + character = character, + weapon = id, + ) - Property.DEXTERITY -> { - val related = - context.getString(R.string.character_sheet_stat_dexterity) - Roll.Bonus( - title = context.getString( - R.string.dice_roll_bonus_detail, - related - ), - value = model.dexterity.modifier, - ) - } + Attack.Type.PHYSICAL_RANGE_ATTACK -> DiceThrow.PhysicalRangeDamage( + character = character, + weapon = id, + ) - Property.CONSTITUTION -> { - val related = - context.getString(R.string.character_sheet_stat_constitution) - Roll.Bonus( - title = context.getString( - R.string.dice_roll_bonus_detail, - related - ), - value = model.constitution.modifier, - ) - } - - Property.INTELLIGENCE -> { - val related = - context.getString(R.string.character_sheet_stat_intelligence) - Roll.Bonus( - title = context.getString( - R.string.dice_roll_bonus_detail, - related - ), - value = model.intelligence.modifier, - ) - } - - Property.WISDOM -> { - val related = - context.getString(R.string.character_sheet_stat_wisdom) - Roll.Bonus( - title = context.getString( - R.string.dice_roll_bonus_detail, - related - ), - value = model.wisdom.modifier, - ) - } - - Property.CHARISMA -> { - val related = context.getString(R.string.character_sheet_stat_charisma) - Roll.Bonus( - title = context.getString( - R.string.dice_roll_bonus_detail, - related - ), - value = model.charisma.modifier, - ) - } - - else -> null - } - } ?: emptyList()) + alterations.map { it.bonus }.flatten(), - ) + else -> null + } } } \ No newline at end of file diff --git a/app/src/main/java/com/pixelized/rplexicon/ui/screens/character/pages/actions/SpellsActionViewModel.kt b/app/src/main/java/com/pixelized/rplexicon/ui/screens/character/pages/actions/SpellsActionViewModel.kt index 1591f9d..a73a1e9 100644 --- a/app/src/main/java/com/pixelized/rplexicon/ui/screens/character/pages/actions/SpellsActionViewModel.kt +++ b/app/src/main/java/com/pixelized/rplexicon/ui/screens/character/pages/actions/SpellsActionViewModel.kt @@ -1,7 +1,6 @@ package com.pixelized.rplexicon.ui.screens.character.pages.actions import android.app.Application -import androidx.annotation.StringRes import androidx.compose.runtime.State import androidx.compose.runtime.mutableStateOf import androidx.lifecycle.AndroidViewModel @@ -9,9 +8,8 @@ import androidx.lifecycle.SavedStateHandle import androidx.lifecycle.viewModelScope import com.pixelized.rplexicon.R import com.pixelized.rplexicon.model.CharacterSheet +import com.pixelized.rplexicon.model.DiceThrow import com.pixelized.rplexicon.model.Property -import com.pixelized.rplexicon.model.Roll -import com.pixelized.rplexicon.model.Spell import com.pixelized.rplexicon.model.Throw import com.pixelized.rplexicon.repository.data.CharacterSheetRepository import com.pixelized.rplexicon.repository.data.SpellRepository @@ -21,12 +19,10 @@ import com.pixelized.rplexicon.ui.screens.character.composable.actions.SpellLeve import com.pixelized.rplexicon.ui.screens.character.composable.actions.SpellUio import com.pixelized.rplexicon.ui.screens.character.factory.SpellUioFactory import com.pixelized.rplexicon.ui.screens.character.pages.chooser.SpellChooserUio -import com.pixelized.rplexicon.utilitary.extentions.context import com.pixelized.rplexicon.utilitary.extentions.icon import com.pixelized.rplexicon.utilitary.extentions.local.firstSpellSlot import com.pixelized.rplexicon.utilitary.extentions.local.highestSpellLevel import com.pixelized.rplexicon.utilitary.extentions.local.spell -import com.pixelized.rplexicon.utilitary.extentions.local.toString import com.pixelized.rplexicon.utilitary.extentions.modifier import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.Dispatchers @@ -53,6 +49,7 @@ class SpellsActionViewModel @Inject constructor( val preparedSpellLevel: State get() = _preparedSpellLevel init { + // TODO rework that part. use factory viewModelScope.launch { launch { spellRepository.spells.collect { @@ -117,7 +114,7 @@ class SpellsActionViewModel @Inject constructor( } fun shouldDisplaySpellLevelChooser(name: String): Boolean { - val assignedSpell = spellRepository.findAssignedSpell( + val assignedSpell = spellRepository.find( character = character, spell = name, ) @@ -128,7 +125,7 @@ class SpellsActionViewModel @Inject constructor( } fun prepareSpellCast(name: String) { - val assignedSpell = spellRepository.findAssignedSpell( + val assignedSpell = spellRepository.find( character = character, spell = name, ) @@ -160,11 +157,8 @@ class SpellsActionViewModel @Inject constructor( } } - fun onCastSpell(id: String): Roll { - val spell = spellRepository.findAssignedSpell( - character = character, - spell = id, - ) + fun onCastSpell(id: String): DiceThrow { + val spell = spellRepository.find(character = character, spell = id) return onCastSpell( id = id, level = when (model.isWarlock) { @@ -174,135 +168,52 @@ class SpellsActionViewModel @Inject constructor( ) } - fun onCastSpell(id: String, level: Int): Roll { - val spell = spellRepository.findAssignedSpell( - character = character, - spell = id, - ) - return spellRoll( - spell = spell?.spell, - titleRes = R.string.dice_roll_spell_cast, - throws = spell?.effect, - level = level, - levelThrow = spell?.level, - ) + fun onCastSpell(id: String, level: Int): DiceThrow { + return DiceThrow.SpellEffect(character = character, spell = id, level = level) } - fun onSpellHitRoll(id: String): Roll { - val spell = spellRepository.findAssignedSpell( - character = character, - spell = id, - ) - return spellRoll( - spell = spell?.spell, - titleRes = R.string.dice_roll_attack_hit_title, - throws = spell?.hit, - ) + fun onSpellHitRoll(id: String): DiceThrow { + return DiceThrow.SpellAttack(character = character, spell = id) } - fun onSpellDamageRoll(id: String): Roll { - val spell = spellRepository.findAssignedSpell( - character = character, - spell = id, - ) - return spellRoll( - spell = spell?.spell, - titleRes = R.string.dice_roll_attack_damage_title, - throws = spell?.effect, - canUseCriticalDice = true, - ) + fun onSpellDamageRoll(id: String): DiceThrow { + return DiceThrow.SpellDamage(character = character, spell = id) } - //////////////////////////////////////// - // region: Helpers + /** + * Helper method to build a readable String from a Throw. + * Create a string following the format "amount 'd' faces '+' modifiers + amount * level 'd' faces" + * where modifier is a integer, sum of all modifiers values. + * ex : for Sleep level 2 -> 5d8+4 + 2d8 + */ + private fun Throw.toString(character: CharacterSheet, level: Int = 0): String? { + val modifierLabel = modifier + .sumOf { modifier -> modifier.toValue(character = character) } + .let { if (it > 0) "+${it * level}" else "" } + return when (level > 0) { + true -> "${amount * level}d${faces}${modifierLabel}" + else -> null + } + } - private fun spellRoll( - spell: Spell?, - @StringRes titleRes: Int, - throws: Throw?, - level: Int = 0, - levelThrow: Throw? = null, - canUseCriticalDice: Boolean = false, - ): Roll { - // build the title - val title = context.getString(titleRes, spell?.name) - // build the roll - return Roll( - character = character, - canUseCriticalDice = canUseCriticalDice, - title = title, - highlight = spell?.name, - dices = listOf( - Roll.Dice( - title = spell?.name, - advantage = false, - disadvantage = false, - fail = false, - count = throws?.amount ?: 1, - faces = throws?.faces ?: 20, - ) - ) + List(max(0, level - (spell?.level ?: 0))) { - Roll.Dice( - title = context.getString( - R.string.spell_level_chooser_label, - ((spell?.level ?: 0) + it + 1).toString() - ), - advantage = false, - disadvantage = false, - fail = false, - count = levelThrow?.amount ?: 1, - faces = levelThrow?.faces ?: 20, - ) - }, - bonus = (throws?.modifier - ?.mapNotNull { - when (it) { - Property.PROFICIENCY -> { - Roll.Bonus( - title = context.getString(R.string.dice_roll_proficiency_bonus), - value = model.proficiency, - ) - } - - Property.INTELLIGENCE -> { - val related = - context.getString(R.string.character_sheet_stat_intelligence) - Roll.Bonus( - title = context.getString( - R.string.dice_roll_bonus_detail, - related - ), - value = model.intelligence.modifier, - ) - } - - Property.WISDOM -> { - val related = - context.getString(R.string.character_sheet_stat_wisdom) - Roll.Bonus( - title = context.getString( - R.string.dice_roll_bonus_detail, - related - ), - value = model.wisdom.modifier, - ) - } - - Property.CHARISMA -> { - val related = - context.getString(R.string.character_sheet_stat_charisma) - Roll.Bonus( - title = context.getString( - R.string.dice_roll_bonus_detail, - related - ), - value = model.charisma.modifier, - ) - } - - else -> null - } - } ?: emptyList()), - ) + private fun Property.toValue( + character: CharacterSheet, + ): Int { + return when (this) { + Property.PROFICIENCY -> character.proficiency + Property.STRENGTH -> character.strength.modifier + Property.STRENGTH_THROW -> character.strength.modifier + Property.DEXTERITY -> character.dexterity.modifier + Property.DEXTERITY_THROW -> character.dexterity.modifier + Property.CONSTITUTION -> character.constitution.modifier + Property.CONSTITUTION_THROW -> character.constitution.modifier + Property.INTELLIGENCE -> character.intelligence.modifier + Property.INTELLIGENCE_THROW -> character.intelligence.modifier + Property.WISDOM -> character.wisdom.modifier + Property.WISDOM_THROW -> character.wisdom.modifier + Property.CHARISMA -> character.charisma.modifier + Property.CHARISMA_THROW -> character.charisma.modifier + else -> 0 + } } } \ No newline at end of file diff --git a/app/src/main/java/com/pixelized/rplexicon/ui/screens/character/pages/proficiency/ProficiencyPage.kt b/app/src/main/java/com/pixelized/rplexicon/ui/screens/character/pages/proficiency/ProficiencyPage.kt index b91c5c2..de7a1b1 100644 --- a/app/src/main/java/com/pixelized/rplexicon/ui/screens/character/pages/proficiency/ProficiencyPage.kt +++ b/app/src/main/java/com/pixelized/rplexicon/ui/screens/character/pages/proficiency/ProficiencyPage.kt @@ -64,50 +64,15 @@ fun ProficiencyPage( modifier = Modifier.fillMaxWidth(), sheet = sheet, onInitiative = { - val roll = viewModel.initiativeRoll() - overlay.prepareRoll(roll = roll) + overlay.prepareRoll(diceThrow = viewModel.initiativeRoll()) overlay.showOverlay() }, onStats = { stat -> - val roll = when (stat.id) { - StatUio.ID.STRENGTH -> viewModel.strengthRoll() - StatUio.ID.DEXTERITY -> viewModel.dexterityRoll() - StatUio.ID.CONSTITUTION -> viewModel.constitutionRoll() - StatUio.ID.INTELLIGENCE -> viewModel.intelligenceRoll() - StatUio.ID.WISDOM -> viewModel.wisdomRoll() - StatUio.ID.CHARISMA -> viewModel.charismaRoll() - } - overlay.prepareRoll(roll = roll) + overlay.prepareRoll(diceThrow = viewModel.statRoll(stat.id)) overlay.showOverlay() }, onProficiencies = { proficiency -> - val roll = when (proficiency.id) { - ProficiencyUio.ID.STRENGTH_SAVING_THROW -> viewModel.strengthSavingThrowsRoll() - ProficiencyUio.ID.DEXTERITY_SAVING_THROW -> viewModel.dexteritySavingThrowsRoll() - ProficiencyUio.ID.CONSTITUTION_SAVING_THROW -> viewModel.constitutionSavingThrowsRoll() - ProficiencyUio.ID.INTELLIGENCE_SAVING_THROW -> viewModel.intelligenceSavingThrowsRoll() - ProficiencyUio.ID.WISDOM_SAVING_THROW -> viewModel.wisdomSavingThrowsRoll() - ProficiencyUio.ID.CHARISMA_SAVING_THROW -> viewModel.charismaSavingThrowsRoll() - ProficiencyUio.ID.ACROBATICS -> viewModel.acrobaticsRoll() - ProficiencyUio.ID.ANIMAL_HANDLING -> viewModel.animalHandlingRoll() - ProficiencyUio.ID.ARCANA -> viewModel.arcanaRoll() - ProficiencyUio.ID.ATHLETICS -> viewModel.athleticsRoll() - ProficiencyUio.ID.DECEPTION -> viewModel.deceptionRoll() - ProficiencyUio.ID.HISTORY -> viewModel.historyRoll() - ProficiencyUio.ID.INSIGHT -> viewModel.insightRoll() - ProficiencyUio.ID.INTIMIDATION -> viewModel.intimidationRoll() - ProficiencyUio.ID.INVESTIGATION -> viewModel.investigationRoll() - ProficiencyUio.ID.MEDICINE -> viewModel.medicineRoll() - ProficiencyUio.ID.NATURE -> viewModel.natureRoll() - ProficiencyUio.ID.PERCEPTION -> viewModel.perceptionRoll() - ProficiencyUio.ID.PERFORMANCE -> viewModel.performanceRoll() - ProficiencyUio.ID.PERSUASION -> viewModel.persuasionRoll() - ProficiencyUio.ID.RELIGION -> viewModel.religionRoll() - ProficiencyUio.ID.SLEIGHT_OF_HAND -> viewModel.sleightOfHandRoll() - ProficiencyUio.ID.STEALTH -> viewModel.stealthRoll() - ProficiencyUio.ID.SURVIVAL -> viewModel.survivalRoll() - } - overlay.prepareRoll(roll = roll) + overlay.prepareRoll(diceThrow = viewModel.proficiencyRoll(proficiency.id)) overlay.showOverlay() }, ) diff --git a/app/src/main/java/com/pixelized/rplexicon/ui/screens/character/pages/proficiency/ProficiencyViewModel.kt b/app/src/main/java/com/pixelized/rplexicon/ui/screens/character/pages/proficiency/ProficiencyViewModel.kt index 5e278b8..12847f4 100644 --- a/app/src/main/java/com/pixelized/rplexicon/ui/screens/character/pages/proficiency/ProficiencyViewModel.kt +++ b/app/src/main/java/com/pixelized/rplexicon/ui/screens/character/pages/proficiency/ProficiencyViewModel.kt @@ -1,23 +1,19 @@ package com.pixelized.rplexicon.ui.screens.character.pages.proficiency import android.app.Application -import androidx.annotation.StringRes import androidx.compose.runtime.State import androidx.compose.runtime.mutableStateOf import androidx.lifecycle.AndroidViewModel import androidx.lifecycle.SavedStateHandle import androidx.lifecycle.viewModelScope -import com.pixelized.rplexicon.R -import com.pixelized.rplexicon.model.CharacterSheet -import com.pixelized.rplexicon.model.Property -import com.pixelized.rplexicon.model.Roll -import com.pixelized.rplexicon.model.d20 +import com.pixelized.rplexicon.model.DiceThrow import com.pixelized.rplexicon.repository.data.AlterationRepository import com.pixelized.rplexicon.repository.data.CharacterSheetRepository import com.pixelized.rplexicon.ui.navigation.screens.characterSheetArgument +import com.pixelized.rplexicon.ui.screens.character.composable.character.ProficiencyUio +import com.pixelized.rplexicon.ui.screens.character.composable.character.StatUio import com.pixelized.rplexicon.ui.screens.character.factory.CharacterSheetUioFactory -import com.pixelized.rplexicon.utilitary.extentions.context -import com.pixelized.rplexicon.utilitary.extentions.modifier +import com.pixelized.rplexicon.utilitary.extentions.local.toActiveStatus import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch @@ -26,14 +22,13 @@ import javax.inject.Inject @HiltViewModel class ProficiencyViewModel @Inject constructor( - application: Application, - savedStateHandle: SavedStateHandle, private val characterRepository: CharacterSheetRepository, private val alterationRepository: AlterationRepository, private val characterSheetFactory: CharacterSheetUioFactory, + savedStateHandle: SavedStateHandle, + application: Application, ) : AndroidViewModel(application) { private val character = savedStateHandle.characterSheetArgument.name - private var model: CharacterSheet = characterRepository.data.value.getValue(character) private val _sheet = mutableStateOf(null) val sheet: State get() = _sheet @@ -43,383 +38,52 @@ class ProficiencyViewModel @Inject constructor( launch { characterRepository.data.collect { _sheet.value = withContext(Dispatchers.Default) { - model = it.getValue(key = character) - characterSheetFactory.convert(sheet = model) + val alterations = alterationRepository.getAlterations(character) + characterSheetFactory.convert( + sheet = it.getValue(key = character), + alterations = alterations.toActiveStatus() + ) } } } } } - fun initiativeRoll(): Roll = abilityRoll( - abilityRes = R.string.character_sheet_title_initiative, - relatedRes = R.string.character_sheet_stat_dexterity, - masteryLevel = null, - abilityValue = model.dexterity, - property = Property.INITIATIVE, - ) + fun initiativeRoll(): DiceThrow.Initiative = DiceThrow.Initiative(character) - fun strengthRoll(): Roll = statRoll( - abilityRes = R.string.character_sheet_stat_strength, - abilityValue = model.strength, - property = Property.STRENGTH, - ) - - fun dexterityRoll(): Roll = statRoll( - abilityRes = R.string.character_sheet_stat_dexterity, - abilityValue = model.dexterity, - property = Property.DEXTERITY, - ) - - fun constitutionRoll(): Roll = statRoll( - abilityRes = R.string.character_sheet_stat_constitution, - abilityValue = model.constitution, - property = Property.CONSTITUTION, - ) - - fun intelligenceRoll(): Roll = statRoll( - abilityRes = R.string.character_sheet_stat_intelligence, - abilityValue = model.intelligence, - property = Property.INTELLIGENCE, - ) - - fun wisdomRoll(): Roll = statRoll( - abilityRes = R.string.character_sheet_stat_wisdom, - abilityValue = model.wisdom, - property = Property.WISDOM, - ) - - fun charismaRoll(): Roll = statRoll( - abilityRes = R.string.character_sheet_stat_charisma, - abilityValue = model.charisma, - property = Property.CHARISMA, - ) - - fun strengthSavingThrowsRoll(): Roll = savingThrowRoll( - abilityRes = R.string.character_sheet_stat_strength, - abilityValue = model.strength, - masteryLevel = model.strengthSavingThrows, - property = Property.STRENGTH_SAVING_THROW, - ) - - fun dexteritySavingThrowsRoll(): Roll = savingThrowRoll( - abilityRes = R.string.character_sheet_stat_dexterity, - abilityValue = model.dexterity, - masteryLevel = model.dexteritySavingThrows, - property = Property.DEXTERITY_SAVING_THROW, - ) - - fun constitutionSavingThrowsRoll(): Roll = savingThrowRoll( - abilityRes = R.string.character_sheet_stat_constitution, - abilityValue = model.constitution, - masteryLevel = model.constitutionSavingThrows, - property = Property.CONSTITUTION_SAVING_THROW, - ) - - fun intelligenceSavingThrowsRoll(): Roll = savingThrowRoll( - abilityRes = R.string.character_sheet_stat_intelligence, - abilityValue = model.intelligence, - masteryLevel = model.intelligenceSavingThrows, - property = Property.INTELLIGENCE_SAVING_THROW, - ) - - fun wisdomSavingThrowsRoll(): Roll = savingThrowRoll( - abilityRes = R.string.character_sheet_stat_wisdom, - abilityValue = model.wisdom, - masteryLevel = model.wisdomSavingThrows, - property = Property.WISDOM_SAVING_THROW, - ) - - fun charismaSavingThrowsRoll(): Roll = savingThrowRoll( - abilityRes = R.string.character_sheet_stat_charisma, - abilityValue = model.charisma, - masteryLevel = model.charismaSavingThrows, - property = Property.CHARISMA_SAVING_THROW, - ) - - fun acrobaticsRoll(): Roll = abilityRoll( - abilityRes = R.string.character_sheet_proficiency_acrobatics, - relatedRes = R.string.character_sheet_stat_dexterity, - abilityValue = model.dexterity, - masteryLevel = model.acrobatics, - property = Property.ACROBATICS, - ) - - fun animalHandlingRoll(): Roll = abilityRoll( - abilityRes = R.string.character_sheet_proficiency_animal_handling, - relatedRes = R.string.character_sheet_stat_wisdom, - abilityValue = model.wisdom, - masteryLevel = model.animalHandling, - property = Property.ANIMAL_HANDLING, - ) - - fun arcanaRoll(): Roll = abilityRoll( - abilityRes = R.string.character_sheet_proficiency_arcana, - relatedRes = R.string.character_sheet_stat_intelligence, - abilityValue = model.intelligence, - masteryLevel = model.arcana, - property = Property.ARCANA, - ) - - fun athleticsRoll(): Roll = abilityRoll( - abilityRes = R.string.character_sheet_proficiency_athletics, - relatedRes = R.string.character_sheet_stat_strength, - abilityValue = model.strength, - masteryLevel = model.athletics, - property = Property.ATHLETICS, - ) - - fun deceptionRoll(): Roll = abilityRoll( - abilityRes = R.string.character_sheet_proficiency_deception, - relatedRes = R.string.character_sheet_stat_charisma, - abilityValue = model.charisma, - masteryLevel = model.deception, - property = Property.DECEPTION, - ) - - fun historyRoll(): Roll = abilityRoll( - abilityRes = R.string.character_sheet_proficiency_history, - relatedRes = R.string.character_sheet_stat_intelligence, - abilityValue = model.intelligence, - masteryLevel = model.history, - property = Property.HISTORY, - ) - - fun insightRoll(): Roll = abilityRoll( - abilityRes = R.string.character_sheet_proficiency_insight, - relatedRes = R.string.character_sheet_stat_wisdom, - abilityValue = model.wisdom, - masteryLevel = model.insight, - property = Property.INSIGHT, - ) - - fun intimidationRoll(): Roll = abilityRoll( - abilityRes = R.string.character_sheet_proficiency_intimidation, - relatedRes = R.string.character_sheet_stat_charisma, - abilityValue = model.charisma, - masteryLevel = model.intimidation, - property = Property.INTIMIDATION, - ) - - fun investigationRoll(): Roll = abilityRoll( - abilityRes = R.string.character_sheet_proficiency_investigation, - relatedRes = R.string.character_sheet_stat_intelligence, - abilityValue = model.intelligence, - masteryLevel = model.investigation, - property = Property.INVESTIGATION, - ) - - fun medicineRoll(): Roll = abilityRoll( - abilityRes = R.string.character_sheet_proficiency_medicine, - relatedRes = R.string.character_sheet_stat_wisdom, - abilityValue = model.wisdom, - masteryLevel = model.medicine, - property = Property.MEDICINE, - ) - - fun natureRoll(): Roll = abilityRoll( - abilityRes = R.string.character_sheet_proficiency_nature, - relatedRes = R.string.character_sheet_stat_intelligence, - abilityValue = model.intelligence, - masteryLevel = model.nature, - property = Property.NATURE, - ) - - fun perceptionRoll(): Roll = abilityRoll( - abilityRes = R.string.character_sheet_proficiency_perception, - relatedRes = R.string.character_sheet_stat_wisdom, - abilityValue = model.wisdom, - masteryLevel = model.perception, - property = Property.PERCEPTION, - ) - - fun performanceRoll(): Roll = abilityRoll( - abilityRes = R.string.character_sheet_proficiency_performance, - relatedRes = R.string.character_sheet_stat_charisma, - abilityValue = model.charisma, - masteryLevel = model.performance, - property = Property.PERFORMANCE, - ) - - fun persuasionRoll(): Roll = abilityRoll( - abilityRes = R.string.character_sheet_proficiency_persuasion, - relatedRes = R.string.character_sheet_stat_charisma, - abilityValue = model.charisma, - masteryLevel = model.persuasion, - property = Property.PERSUASION, - ) - - fun religionRoll(): Roll = abilityRoll( - abilityRes = R.string.character_sheet_proficiency_religion, - relatedRes = R.string.character_sheet_stat_intelligence, - abilityValue = model.intelligence, - masteryLevel = model.religion, - property = Property.RELIGION, - ) - - fun sleightOfHandRoll(): Roll = abilityRoll( - abilityRes = R.string.character_sheet_proficiency_sleight_of_hand, - relatedRes = R.string.character_sheet_stat_dexterity, - abilityValue = model.dexterity, - masteryLevel = model.sleightOfHand, - property = Property.SLEIGHT_OF_HAND, - ) - - fun stealthRoll(): Roll = abilityRoll( - abilityRes = R.string.character_sheet_proficiency_stealth, - relatedRes = R.string.character_sheet_stat_dexterity, - abilityValue = model.dexterity, - masteryLevel = model.stealth, - property = Property.STEALTH, - ) - - fun survivalRoll(): Roll = abilityRoll( - abilityRes = R.string.character_sheet_proficiency_survival, - relatedRes = R.string.character_sheet_stat_wisdom, - abilityValue = model.wisdom, - masteryLevel = model.survival, - property = Property.SURVIVAL, - ) - - private fun statRoll( - abilityRes: Int, - abilityValue: Int, - property: Property, - ): Roll { - val ability = context.getString(abilityRes) - // get the alteration for a given player - val alterations = alterationRepository.getStatus( - character = model.name, - property = property, - ) - // check if any alteration give us advantage or disadvantage - val advantage = alterations.any { it.advantage } - val disadvantage = alterations.any { it.disadvantage } - val fail = alterations.any { it.fail } - - return Roll( - character = character, - canUseCriticalDice = false, - title = context.getString(R.string.dice_roll_check_title, ability.uppercase()), - highlight = ability, - dices = listOf( - d20( - title = context.getString(R.string.dice_roll_check_detail, ability), - advantage = advantage, - disadvantage = disadvantage, - fail = fail, - ), - ) + alterations.map { it.dices }.flatten(), - bonus = listOf( - Roll.Bonus( - title = context.getString(R.string.dice_roll_bonus_detail, ability), - value = abilityValue.modifier - ) - ) + alterations.map { it.bonus }.flatten(), - ) + fun statRoll(id: StatUio.ID): DiceThrow = when (id) { + StatUio.ID.STRENGTH -> DiceThrow.Strength(character) + StatUio.ID.DEXTERITY -> DiceThrow.Dexterity(character) + StatUio.ID.CONSTITUTION -> DiceThrow.Constitution(character) + StatUio.ID.INTELLIGENCE -> DiceThrow.Intelligence(character) + StatUio.ID.WISDOM -> DiceThrow.Wisdom(character) + StatUio.ID.CHARISMA -> DiceThrow.Charisma(character) } - private fun savingThrowRoll( - @StringRes abilityRes: Int, - abilityValue: Int, - masteryLevel: Int, - masteryValue: Int = model.proficiency, - property: Property, - ): Roll { - val ability = context.getString(abilityRes) - - // get the alteration for a given player - val alterations = alterationRepository.getStatus( - character = model.name, - property = property, - ) - // check if any alteration give us advantage or disadvantage - val advantage = alterations.any { it.advantage } - val disadvantage = alterations.any { it.disadvantage } - val fail = alterations.any { it.fail } - - return Roll( - character = character, - canUseCriticalDice = false, - title = context.getString( - R.string.dice_roll_saving_throw_title, - ability.uppercase() - ), - highlight = ability, - dices = listOf( - d20( - title = context.getString(R.string.dice_roll_saving_throw_detail, ability), - advantage = advantage, - disadvantage = disadvantage, - fail = fail, - ), - ) + alterations.map { it.dices }.flatten(), - bonus = listOf( - Roll.Bonus( - title = context.getString( - if (masteryLevel == 2) R.string.dice_roll_mastery_expertise else R.string.dice_roll_mastery_proficiency, - context.getString(R.string.dice_roll_mastery_saving_throw) - ), - value = masteryValue * masteryLevel, - ), - Roll.Bonus( - title = context.getString(R.string.dice_roll_bonus_detail, ability), - value = abilityValue.modifier - ), - ) + alterations.map { it.bonus }.flatten(), - ) - } - - private fun abilityRoll( - @StringRes abilityRes: Int, - @StringRes relatedRes: Int, - abilityValue: Int, - masteryLevel: Int?, - masteryValue: Int = model.proficiency, - property: Property, - ): Roll { - val ability = context.getString(abilityRes) - val related = context.getString(relatedRes) - - // get the alteration for a given player - val alterations = alterationRepository.getStatus( - character = model.name, - property = property, - ) - // check if any alteration give us advantage or disadvantage - val advantage = alterations.any { it.advantage } - val disadvantage = alterations.any { it.disadvantage } - val fail = alterations.any { it.fail } - - return Roll( - character = character, - canUseCriticalDice = false, - title = context.getString(R.string.dice_roll_check_title, ability.uppercase()), - highlight = ability, - dices = listOf( - d20( - title = context.getString(R.string.dice_roll_check_detail, ability), - advantage = advantage, - disadvantage = disadvantage, - fail = fail, - ), - ) + alterations.map { it.dices }.flatten(), - bonus = (masteryLevel?.let { - listOf( - Roll.Bonus( - title = context.getString( - if (masteryLevel == 2) R.string.dice_roll_mastery_expertise else R.string.dice_roll_mastery_proficiency, - ability - ), - value = masteryValue * masteryLevel, - ) - ) - } ?: emptyList()) + listOf( - Roll.Bonus( - title = context.getString(R.string.dice_roll_bonus_detail, related), - value = abilityValue.modifier - ) - ) + alterations.map { it.bonus }.flatten(), - ) + fun proficiencyRoll(id: ProficiencyUio.ID): DiceThrow = when (id) { + ProficiencyUio.ID.STRENGTH_SAVING_THROW -> DiceThrow.StrengthSavingThrow(character) + ProficiencyUio.ID.DEXTERITY_SAVING_THROW -> DiceThrow.DexteritySavingThrow(character) + ProficiencyUio.ID.CONSTITUTION_SAVING_THROW -> DiceThrow.ConstitutionSavingThrow(character) + ProficiencyUio.ID.INTELLIGENCE_SAVING_THROW -> DiceThrow.IntelligenceSavingThrow(character) + ProficiencyUio.ID.WISDOM_SAVING_THROW -> DiceThrow.WisdomSavingThrow(character) + ProficiencyUio.ID.CHARISMA_SAVING_THROW -> DiceThrow.CharismaSavingThrow(character) + ProficiencyUio.ID.ACROBATICS -> DiceThrow.Acrobatics(character) + ProficiencyUio.ID.ANIMAL_HANDLING -> DiceThrow.AnimalHandling(character) + ProficiencyUio.ID.ARCANA -> DiceThrow.Arcana(character) + ProficiencyUio.ID.ATHLETICS -> DiceThrow.Athletics(character) + ProficiencyUio.ID.DECEPTION -> DiceThrow.Deception(character) + ProficiencyUio.ID.HISTORY -> DiceThrow.History(character) + ProficiencyUio.ID.INSIGHT -> DiceThrow.Insight(character) + ProficiencyUio.ID.INTIMIDATION -> DiceThrow.Intimidation(character) + ProficiencyUio.ID.INVESTIGATION -> DiceThrow.Investigation(character) + ProficiencyUio.ID.MEDICINE -> DiceThrow.Medicine(character) + ProficiencyUio.ID.NATURE -> DiceThrow.Nature(character) + ProficiencyUio.ID.PERCEPTION -> DiceThrow.Perception(character) + ProficiencyUio.ID.PERFORMANCE -> DiceThrow.Performance(character) + ProficiencyUio.ID.PERSUASION -> DiceThrow.Persuasion(character) + ProficiencyUio.ID.RELIGION -> DiceThrow.Religion(character) + ProficiencyUio.ID.SLEIGHT_OF_HAND -> DiceThrow.SleightOfHand(character) + ProficiencyUio.ID.STEALTH -> DiceThrow.Stealth(character) + ProficiencyUio.ID.SURVIVAL -> DiceThrow.Survival(character) } } \ No newline at end of file diff --git a/app/src/main/java/com/pixelized/rplexicon/ui/screens/rolls/RollOverlay.kt b/app/src/main/java/com/pixelized/rplexicon/ui/screens/rolls/RollOverlay.kt index 26def2f..fddd7cc 100644 --- a/app/src/main/java/com/pixelized/rplexicon/ui/screens/rolls/RollOverlay.kt +++ b/app/src/main/java/com/pixelized/rplexicon/ui/screens/rolls/RollOverlay.kt @@ -4,6 +4,7 @@ import android.content.res.Configuration.UI_MODE_NIGHT_NO import android.content.res.Configuration.UI_MODE_NIGHT_YES import androidx.activity.compose.BackHandler import androidx.compose.animation.AnimatedContent +import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.ContentTransform import androidx.compose.animation.SizeTransform import androidx.compose.animation.fadeIn @@ -14,6 +15,7 @@ import androidx.compose.animation.togetherWith import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding @@ -57,13 +59,17 @@ import androidx.compose.ui.tooling.preview.PreviewParameterProvider import androidx.compose.ui.unit.Density import androidx.compose.ui.unit.LayoutDirection 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 com.pixelized.rplexicon.LocalRollOverlay import com.pixelized.rplexicon.NO_WINDOW_INSETS import com.pixelized.rplexicon.R -import com.pixelized.rplexicon.model.Roll +import com.pixelized.rplexicon.model.DiceThrow import com.pixelized.rplexicon.ui.composable.BlurredOverlayHostState import com.pixelized.rplexicon.ui.composable.ModalNavigationDrawer +import com.pixelized.rplexicon.ui.screens.rolls.composable.AlterationDetail +import com.pixelized.rplexicon.ui.screens.rolls.composable.AlterationDetailUio import com.pixelized.rplexicon.ui.screens.rolls.composable.RollAlteration import com.pixelized.rplexicon.ui.screens.rolls.composable.RollAlterationUio import com.pixelized.rplexicon.ui.screens.rolls.composable.RollDice @@ -102,16 +108,25 @@ fun RollOverlay( viewModel.roll() }, onCard = { - viewModel.toggleDetail() + viewModel.toggleCardDetail() + }, + onAlterationInfo = { + viewModel.showAlterationDetail(it) }, onAlteration = { viewModel.onAlteration(it) }, ) - BackHandler(enabled = drawer.isOpen) { - scope.launch { drawer.close() } - } + BackHandler( + enabled = drawer.isOpen, + onBack = { scope.launch { drawer.close() } }, + ) + + AlterationDetailHandler( + detail = viewModel.alterationDetail, + onDismissRequest = { viewModel.hideAlterationDetail() }, + ) } @OptIn(ExperimentalMaterial3Api::class) @@ -119,7 +134,7 @@ fun RollOverlay( private fun RollOverlayContent( modifier: Modifier = Modifier, drawer: DrawerState, - dice: State, + dice: State, card: State, alterations: State>, showDetail: State, @@ -128,6 +143,7 @@ private fun RollOverlayContent( onClose: () -> Unit, onDice: () -> Unit, onCard: () -> Unit, + onAlterationInfo: (id: String) -> Unit, onAlteration: (id: String) -> Unit, ) { val density = LocalDensity.current @@ -166,11 +182,13 @@ private fun RollOverlayContent( modifier = Modifier .weight(weight = 1f) .fillMaxWidth(4f / 5f), + contentPadding = PaddingValues(vertical = 8.dp), ) { items(items = alterations.value) { RollAlteration( modifier = Modifier.fillMaxWidth(), alteration = it, + onInfo = onAlterationInfo, onClick = onAlteration, ) } @@ -215,11 +233,24 @@ private fun RollOverlayContent( RollDice( modifier = Modifier .align(alignment = Alignment.Center) - .padding(horizontal = 16.dp), + .padding(horizontal = 16.dp) + .padding(bottom = 128.dp), dice = dice, onDice = onDice, ) + AnimatedVisibility( + modifier = Modifier.align(alignment = Alignment.BottomEnd), + visible = enableDrawer.value, + ) { + TextButton(onClick = onMenu) { + Text( + modifier = Modifier.padding(horizontal = 8.dp), + text = stringResource(id = R.string.dice_roll_alteraton_action), + ) + } + } + AnimatedContent( modifier = Modifier .fillMaxWidth() @@ -233,24 +264,13 @@ private fun RollOverlayContent( when (it) { null -> Box( modifier = Modifier.fillMaxWidth(), - contentAlignment = Alignment.BottomEnd, - ) { - if (enableDrawer.value) { - TextButton( - onClick = onMenu, - ) { - Text( - modifier = Modifier.padding(horizontal = 8.dp), - text = stringResource(id = R.string.dice_roll_alteraton_action), - ) - } - } - } + ) else -> ThrowsCard( modifier = Modifier .fillMaxWidth() .padding(all = 16.dp) + .padding(bottom = 32.dp) .clickable(onClick = onCard), throws = it, showDetail = showDetail, @@ -264,6 +284,24 @@ private fun RollOverlayContent( ) } +@Composable +private fun AlterationDetailHandler( + detail: State, + onDismissRequest: () -> Unit, +) { + detail.value?.let { + Dialog( + properties = remember { DialogProperties(usePlatformDefaultWidth = false) }, + onDismissRequest = onDismissRequest, + ) { + AlterationDetail( + detail = it, + onClose = onDismissRequest, + ) + } + } +} + @Composable @Preview(uiMode = UI_MODE_NIGHT_NO) @Preview(uiMode = UI_MODE_NIGHT_YES) @@ -280,7 +318,11 @@ private fun RollOverlayPreview( alterations = remember { mutableStateOf( listOf( - RollAlterationUio(label = "Critique", checked = true), + RollAlterationUio( + label = "Critique", + haveDescription = true, + checked = true + ), ) ) }, @@ -292,6 +334,7 @@ private fun RollOverlayPreview( onClose = { }, onDice = { }, onCard = { }, + onAlterationInfo = { }, onAlteration = { }, ) } @@ -369,7 +412,7 @@ private class RollOverlayPreviewProvider : PreviewParameterProvider>(emptyList()) val alterations: State> get() = _alterations - private val _dice = mutableStateOf(RollDiceUio()) - val dice: State get() = _dice + private val _dice = mutableStateOf(null) + val dice: State get() = _dice private val _card = mutableStateOf(null) val card: State get() = _card @@ -45,18 +44,14 @@ class RollOverlayViewModel @Inject constructor( private val _showDetail = mutableStateOf(true) val showDetail: State get() = _showDetail - fun prepareRoll(roll: Roll) { - this.roll = roll - this.character = characterSheetRepository.find(name = roll.character) + private val _alterationDetail = mutableStateOf(null) + val alterationDetail: State get() = _alterationDetail - _dice.value = RollDiceUio(icon = roll.dices.maxOf { it.faces }.icon) + fun prepareRoll(diceThrow: DiceThrow) { + this.diceThrow = diceThrow _card.value = null - - _alterations.value = if (roll.canUseCriticalDice) { - listOf(RollAlterationUio(label = criticalLabel, checked = false)) - } else { - emptyList() - } + _dice.value = diceFactory.convertDiceThrow(diceThrow) + _alterations.value = alterationFactory.convertDiceThrow(diceThrow) } fun onAlteration(id: String) { @@ -69,31 +64,47 @@ class RollOverlayViewModel @Inject constructor( } fun roll() { - rollJob?.cancel() - rollJob = viewModelScope.launch { - val isCritical = _alterations.value.firstOrNull { it.label == criticalLabel }?.checked ?: false - val (dice, card) = factory.roll( - roll = roll, - diceMultiplier = when { - isCritical -> character?.criticalModifier ?: 2 - else -> 1 - }, - ) - // Start the roll animation. - _dice.value = _dice.value.copy( - animationSpec = RollDiceUio.TWEEN, - rotation = _dice.value.rotation + 5f, - ) - delay(RollDiceUio.ROLL_DURATION) - // Roll the d20 value. - dice?.let { _dice.value = it } - delay(250) - // Display the roll card. - _card.value = card + diceThrow?.let { diceThrow -> + rollJob?.cancel() + rollJob = viewModelScope.launch { + val result = rollUseCase.roll( + diceThrow = diceThrow, + alterationId = _alterations.value.mapNotNull { if (it.checked) it.label else null }, + ) + // Start the roll animation. + _dice.value = _dice.value?.let { + it.copy( + animationSpec = RollDiceUio.TWEEN, + rotation = it.rotation + 5f, + ) + } + delay(RollDiceUio.ROLL_DURATION) + // Roll the d20 value. + result?.dice?.let { _dice.value = it } + delay(250) + // Display the roll card. + result?.card?.let { _card.value = it } + } } } - fun toggleDetail() { + fun toggleCardDetail() { _showDetail.switch() } + + fun showAlterationDetail(id: String) { + val alteration = diceThrow?.character?.let { character -> + alterationRepository.getAlterations(character = character).firstOrNull { it.name == id } + } + if (alteration != null) { + _alterationDetail.value = AlterationDetailUio( + name = id, + description = alteration.description + ) + } + } + + fun hideAlterationDetail() { + _alterationDetail.value = null + } } \ No newline at end of file diff --git a/app/src/main/java/com/pixelized/rplexicon/ui/screens/rolls/composable/AlterationDetail.kt b/app/src/main/java/com/pixelized/rplexicon/ui/screens/rolls/composable/AlterationDetail.kt new file mode 100644 index 0000000..06c5981 --- /dev/null +++ b/app/src/main/java/com/pixelized/rplexicon/ui/screens/rolls/composable/AlterationDetail.kt @@ -0,0 +1,115 @@ +package com.pixelized.rplexicon.ui.screens.rolls.composable + +import android.content.res.Configuration.UI_MODE_NIGHT_NO +import android.content.res.Configuration.UI_MODE_NIGHT_YES +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.foundation.verticalScroll +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Close +import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Surface +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.Stable +import androidx.compose.runtime.remember +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.text.AnnotatedString +import androidx.compose.ui.text.style.TextOverflow +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import com.pixelized.rplexicon.ui.theme.LexiconTheme +import com.pixelized.rplexicon.utilitary.extentions.lexicon + +@Stable +data class AlterationDetailUio( + val name: String, + val description: String, +) + +@Composable +fun AlterationDetail( + modifier: Modifier = Modifier, + detail: AlterationDetailUio, + onClose: () -> Unit, +) { + Surface( + modifier = Modifier + .padding(all = 24.dp) + .then(other = modifier), + shape = remember { RoundedCornerShape(size = 24.dp) }, + ) { + Column { + Row( + modifier = Modifier.fillMaxWidth().padding(start = 24.dp), + horizontalArrangement = Arrangement.SpaceBetween, + verticalAlignment = Alignment.Top, + ) { + Text( + modifier = Modifier.padding(top = 16.dp), + style = MaterialTheme.typography.titleMedium, + maxLines = 1, + overflow = TextOverflow.Ellipsis, + text = AnnotatedString( + text = detail.name, + spanStyles = listOf( + AnnotatedString.Range( + item = MaterialTheme.lexicon.typography.bodyDropCapSpan, + start = 0, + end = Integer.min(1, detail.name.length), + ) + ) + ), + ) + IconButton(onClick = onClose) { + Icon( + imageVector = Icons.Default.Close, + contentDescription = null + ) + } + } + Text( + modifier = Modifier + .padding(top = 8.dp) + .verticalScroll(rememberScrollState()) + .padding(horizontal = 24.dp) + .padding(top = 8.dp, bottom = 24.dp), + style = MaterialTheme.typography.bodyMedium, + text = detail.description, + ) + } + } +} + +@Composable +@Preview(uiMode = UI_MODE_NIGHT_NO) +@Preview(uiMode = UI_MODE_NIGHT_YES) +private fun AlterationDetailPreview() { + LexiconTheme { + AlterationDetail( + detail = AlterationDetailUio( + name = "Rage", + description = "\"En combat, vous vous battez avec une férocité bestiale. Durant votre tour, vous pouvez entrer en rage en utilisant une action bonus. En rage, vous gagnez les bénéfices suivants si vous ne portez pas d'armure lourde :\n" + + "\n" + + "Vous avez un avantage aux jets de Force et aux jets de sauvegarde de Force.\n" + + "Quand vous effectuez une attaque au corps à corps avec une arme utilisant la Force, vous gagnez un bonus aux jets de dégâts qui dépend de votre niveau de barbare, comme indiqué dans la colonne Dégâts de la table ci-dessus.\n" + + "Vous avez la résistance aux dégâts contondants, perforants et tranchants.\n" + + "Si vous êtes capable de lancer des sorts, vous ne pouvez les lancer ou vous concentrer sur eux pour toute la durée de la rage.\n" + + "\n" + + "Votre rage dure 1 minute. Elle finit prématurément si vous devenez inconscient, ou si votre tour se termine et que vous n'avez ni attaqué une créature hostile, ni subi des dégâts, depuis votre précédent tour. Vous pouvez également mettre fin à votre rage durant votre tour par une action bonus. Vous récupérez les utilisations de rage dépensées après avoir terminé un repos long.\n" + + "\n" + + "Condition \"\"Rage inhibée\"\" :\n" + + "Votre rage vous demande des efforts de concentration ou se dissipe. Vous devez réussir un jet de sauvegarde de constitution dont la difficulté augmente à chaque tour (DC 6 + 2 par tour) sous peine de voir votre rage cesser. Sous certaines conditions, la difficulté pourra augmenter ou baisser au-delà des valeurs indiquées.\"" + ), + onClose = { }, + ) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/pixelized/rplexicon/ui/screens/rolls/composable/RollAlteration.kt b/app/src/main/java/com/pixelized/rplexicon/ui/screens/rolls/composable/RollAlteration.kt index 4bc59f6..579ad4e 100644 --- a/app/src/main/java/com/pixelized/rplexicon/ui/screens/rolls/composable/RollAlteration.kt +++ b/app/src/main/java/com/pixelized/rplexicon/ui/screens/rolls/composable/RollAlteration.kt @@ -7,6 +7,10 @@ import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.outlined.Info +import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Surface import androidx.compose.material3.Switch @@ -24,19 +28,21 @@ import com.pixelized.rplexicon.ui.theme.LexiconTheme @Stable data class RollAlterationUio( val label: String, - val checked: Boolean + val checked: Boolean, + val haveDescription: Boolean, ) @Composable fun RollAlteration( modifier: Modifier = Modifier, alteration: RollAlterationUio, + onInfo: (id: String) -> Unit, onClick: (id: String) -> Unit, ) { Row( modifier = modifier .clickable { onClick(alteration.label) } - .padding(horizontal = 16.dp, vertical = 8.dp), + .padding(horizontal = 16.dp), verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.SpaceBetween, ) { @@ -45,11 +51,21 @@ fun RollAlteration( text = alteration.label, ) - Switch( - enabled = true, - checked = alteration.checked, - onCheckedChange = { onClick(alteration.label) }, - ) + Row { + if (alteration.haveDescription) { + IconButton(onClick = { onInfo(alteration.label) }) { + Icon( + imageVector = Icons.Outlined.Info, + contentDescription = null, + ) + } + } + Switch( + enabled = true, + checked = alteration.checked, + onCheckedChange = { onClick(alteration.label) }, + ) + } } } @@ -64,6 +80,7 @@ private fun RollAlterationPreview( RollAlteration( modifier = Modifier.fillMaxWidth(), alteration = preview, + onInfo = { }, onClick = { }, ) } @@ -72,8 +89,8 @@ private fun RollAlterationPreview( private class RollAlterationPreviewProvider : PreviewParameterProvider { override val values: Sequence = sequenceOf( - RollAlterationUio(label = "Critique", checked = false), - RollAlterationUio(label = "Rage", checked = true), - RollAlterationUio(label = "Bénédiction", checked = false), + RollAlterationUio(label = "Critique", checked = false, haveDescription = false), + RollAlterationUio(label = "Rage", checked = true, haveDescription = true), + RollAlterationUio(label = "Bénédiction", checked = false, haveDescription = false), ) } \ No newline at end of file diff --git a/app/src/main/java/com/pixelized/rplexicon/ui/screens/rolls/composable/RollDice.kt b/app/src/main/java/com/pixelized/rplexicon/ui/screens/rolls/composable/RollDice.kt index 0eb1b9c..1e25d60 100644 --- a/app/src/main/java/com/pixelized/rplexicon/ui/screens/rolls/composable/RollDice.kt +++ b/app/src/main/java/com/pixelized/rplexicon/ui/screens/rolls/composable/RollDice.kt @@ -36,6 +36,7 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.draw.rotate import androidx.compose.ui.graphics.Color import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.Dp @@ -70,7 +71,7 @@ data class RollDiceUio( fun RollDice( modifier: Modifier = Modifier, diceSize: Dp = 64.dp * 2.5f, - dice: State, + dice: State, onDice: () -> Unit, ) { Column( @@ -108,37 +109,37 @@ fun RollDice( @Composable private fun Dice( modifier: Modifier = Modifier, - dice: State, + dice: State, ) { - val animatedRotation = animateFloatAsState( - targetValue = dice.value.rotation * 360, - animationSpec = dice.value.animationSpec, - label = "AnimatedRotation" - ) - Icon( - modifier = modifier.rotate(degrees = animatedRotation.value % 360), - tint = MaterialTheme.colorScheme.primary, - painter = painterResource(id = dice.value.icon), - contentDescription = null, - ) + dice.value?.let { + val animatedRotation = animateFloatAsState( + targetValue = it.rotation * 360, + animationSpec = it.animationSpec, + label = "AnimatedRotation" + ) + Icon( + modifier = modifier.rotate(degrees = animatedRotation.value % 360), + tint = MaterialTheme.colorScheme.primary, + painter = painterResource(id = it.icon), + contentDescription = null, + ) + } } @Composable private fun Result( modifier: Modifier = Modifier, - dice: State, + dice: State, ) { - val colorScheme = MaterialTheme.colorScheme - AnimatedContent( modifier = modifier, - targetState = dice.value.result, + targetState = dice.value?.result, transitionSpec = { val enter = fadeIn() + expandIn(expandFrom = Alignment.Center) { it } val exit = fadeOut() + shrinkOut(shrinkTowards = Alignment.Center) { IntSize.Zero } enter togetherWith exit }, - label = "" + label = "ResultAnimation" ) { when (it) { null -> Box(modifier = Modifier) @@ -158,13 +159,13 @@ private fun Result( @Composable private fun Critical( modifier: Modifier = Modifier, - dice: State, + dice: State, ) { val state = remember { derivedStateOf { when { - dice.value.isCriticalSuccess -> 1 - dice.value.isCriticalFailure -> 2 + dice.value?.isCriticalSuccess == true -> 1 + dice.value?.isCriticalFailure == true -> 2 else -> 0 } } @@ -177,7 +178,7 @@ private fun Critical( val exit = fadeOut() + shrinkOut(shrinkTowards = Alignment.Center) { IntSize.Zero } enter togetherWith exit }, - label = "", + label = "CriticalAnimation", ) { Text( style = MaterialTheme.typography.headlineLarge, @@ -188,8 +189,8 @@ private fun Critical( else -> MaterialTheme.colorScheme.onSurface }, text = when (it) { - 1 -> "SUCCÈS CRITIQUE" - 2 -> "ÉCHEC CRITIQUE" + 1 -> stringResource(id = R.string.generic_critical_success) + 2 -> stringResource(id = R.string.generic_critical_failure) else -> "" }, ) diff --git a/app/src/main/java/com/pixelized/rplexicon/ui/screens/rolls/factory/AlterationFactory.kt b/app/src/main/java/com/pixelized/rplexicon/ui/screens/rolls/factory/AlterationFactory.kt new file mode 100644 index 0000000..b8c1d82 --- /dev/null +++ b/app/src/main/java/com/pixelized/rplexicon/ui/screens/rolls/factory/AlterationFactory.kt @@ -0,0 +1,98 @@ +package com.pixelized.rplexicon.ui.screens.rolls.factory + +import android.app.Application +import com.pixelized.rplexicon.R +import com.pixelized.rplexicon.model.DiceThrow +import com.pixelized.rplexicon.model.Property +import com.pixelized.rplexicon.repository.data.ActionRepository +import com.pixelized.rplexicon.repository.data.AlterationRepository +import com.pixelized.rplexicon.repository.data.SpellRepository +import com.pixelized.rplexicon.ui.screens.rolls.composable.RollAlterationUio +import javax.inject.Inject + +class AlterationFactory @Inject constructor( + private val application: Application, + private val actionRepository: ActionRepository, + private val spellRepository: SpellRepository, + private val alterationRepository: AlterationRepository, +) { + fun convertDiceThrow(diceThrow: DiceThrow): List { + val properties = when (diceThrow) { + is DiceThrow.Initiative -> listOf(Property.INITIATIVE, Property.DEXTERITY) + is DiceThrow.Strength -> listOf(Property.STRENGTH, Property.STRENGTH_THROW) + is DiceThrow.Dexterity -> listOf(Property.DEXTERITY, Property.DEXTERITY_THROW) + is DiceThrow.Constitution -> listOf(Property.CONSTITUTION, Property.CONSTITUTION_THROW) + is DiceThrow.Intelligence -> listOf(Property.INTELLIGENCE, Property.INTELLIGENCE_THROW) + is DiceThrow.Wisdom -> listOf(Property.WISDOM, Property.WISDOM_THROW) + is DiceThrow.Charisma -> listOf(Property.CHARISMA, Property.CHARISMA_THROW) + is DiceThrow.StrengthSavingThrow -> listOf(Property.STRENGTH_SAVING_THROW) + is DiceThrow.DexteritySavingThrow -> listOf(Property.DEXTERITY_SAVING_THROW) + is DiceThrow.ConstitutionSavingThrow -> listOf(Property.CONSTITUTION_SAVING_THROW) + 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.Acrobatics -> listOf(Property.ACROBATICS, Property.DEXTERITY) + is DiceThrow.AnimalHandling -> listOf(Property.ANIMAL_HANDLING, Property.WISDOM) + is DiceThrow.Arcana -> listOf(Property.ARCANA, Property.INTELLIGENCE) + is DiceThrow.Athletics -> listOf(Property.ATHLETICS, Property.STRENGTH) + is DiceThrow.Deception -> listOf(Property.DECEPTION, Property.CHARISMA) + is DiceThrow.History -> listOf(Property.HISTORY, Property.INTELLIGENCE) + is DiceThrow.Insight -> listOf(Property.INSIGHT, Property.WISDOM) + is DiceThrow.Intimidation -> listOf(Property.INTIMIDATION, Property.CHARISMA) + is DiceThrow.Investigation -> listOf(Property.INVESTIGATION, Property.INTELLIGENCE) + is DiceThrow.Medicine -> listOf(Property.MEDICINE, Property.WISDOM) + is DiceThrow.Nature -> listOf(Property.NATURE, Property.INTELLIGENCE) + is DiceThrow.Perception -> listOf(Property.PERCEPTION, Property.WISDOM) + is DiceThrow.Performance -> listOf(Property.PERCEPTION, Property.CHARISMA) + is DiceThrow.Persuasion -> listOf(Property.PERSUASION, Property.CHARISMA) + is DiceThrow.Religion -> listOf(Property.RELIGION, Property.INTELLIGENCE) + is DiceThrow.SleightOfHand -> listOf(Property.SLEIGHT_OF_HAND, Property.DEXTERITY) + is DiceThrow.Stealth -> listOf(Property.STEALTH, Property.DEXTERITY) + is DiceThrow.Survival -> listOf(Property.SURVIVAL, Property.WISDOM) + is DiceThrow.PhysicalMeleeAttack -> { + val action = actionRepository.find(diceThrow.character, action = diceThrow.weapon) + listOf(Property.PHYSICAL_MELEE_ATTACK) + (action?.hit?.modifier ?: emptyList()) + } + + is DiceThrow.PhysicalMeleeDamage -> { + val action = actionRepository.find(diceThrow.character, action = diceThrow.weapon) + listOf(Property.PHYSICAL_MELEE_DAMAGE) + (action?.damage?.modifier ?: emptyList()) + } + + is DiceThrow.PhysicalRangeAttack -> { + val action = actionRepository.find(diceThrow.character, action = diceThrow.weapon) + listOf(Property.PHYSICAL_RANGE_ATTACK) + (action?.hit?.modifier ?: emptyList()) + } + + is DiceThrow.PhysicalRangeDamage -> { + val action = actionRepository.find(diceThrow.character, action = diceThrow.weapon) + listOf(Property.PHYSICAL_RANGE_DAMAGE) + (action?.damage?.modifier ?: emptyList()) + } + + is DiceThrow.SpellAttack -> { + val spell = spellRepository.find(diceThrow.character, spell = diceThrow.spell) + listOf(Property.SPELL_ATTACK) + (spell?.hit?.modifier ?: emptyList()) + } + + is DiceThrow.SpellDamage -> { + val spell = spellRepository.find(diceThrow.character, spell = diceThrow.spell) + listOf(Property.SPELL_DAMAGE) + (spell?.effect?.modifier ?: emptyList()) + } + + is DiceThrow.SpellEffect -> { + val spell = spellRepository.find(diceThrow.character, spell = diceThrow.spell) + spell?.effect?.modifier ?: emptyList() + } + } + + return alterationRepository + .getAlterations(character = diceThrow.character, *properties.toTypedArray()) + .map { + RollAlterationUio( + label = it.name, + checked = it.active, + haveDescription = true, + ) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/pixelized/rplexicon/ui/screens/rolls/factory/DiceFactory.kt b/app/src/main/java/com/pixelized/rplexicon/ui/screens/rolls/factory/DiceFactory.kt new file mode 100644 index 0000000..9ce7a57 --- /dev/null +++ b/app/src/main/java/com/pixelized/rplexicon/ui/screens/rolls/factory/DiceFactory.kt @@ -0,0 +1,46 @@ +package com.pixelized.rplexicon.ui.screens.rolls.factory + +import com.pixelized.rplexicon.R +import com.pixelized.rplexicon.model.DiceThrow +import com.pixelized.rplexicon.repository.data.ActionRepository +import com.pixelized.rplexicon.repository.data.SpellRepository +import com.pixelized.rplexicon.ui.screens.rolls.composable.RollDiceUio +import com.pixelized.rplexicon.utilitary.extentions.icon +import javax.inject.Inject + +class DiceFactory @Inject constructor( + private val actionRepository: ActionRepository, + private val spellRepository: SpellRepository, +) { + fun convertDiceThrow(diceThrow: DiceThrow) = when (diceThrow) { + is DiceThrow.PhysicalMeleeDamage -> actionRepository.find( + character = diceThrow.character, + action = diceThrow.weapon + )?.damage?.faces?.icon?.let { + RollDiceUio(icon = it) + } + + is DiceThrow.PhysicalRangeDamage -> actionRepository.find( + character = diceThrow.character, + action = diceThrow.weapon + )?.damage?.faces?.icon?.let { + RollDiceUio(icon = it) + } + + is DiceThrow.SpellDamage -> spellRepository.find( + character = diceThrow.character, + spell = diceThrow.spell + )?.effect?.faces?.icon?.let { + RollDiceUio(icon = it) + } + + is DiceThrow.SpellEffect -> spellRepository.find( + character = diceThrow.character, + spell = diceThrow.spell + )?.effect?.faces?.icon?.let { + RollDiceUio(icon = it) + } + + else -> RollDiceUio(icon = R.drawable.ic_d20_24) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/pixelized/rplexicon/ui/screens/spell/SpellDetailViewModel.kt b/app/src/main/java/com/pixelized/rplexicon/ui/screens/spell/SpellDetailViewModel.kt index 49655cd..f951dd0 100644 --- a/app/src/main/java/com/pixelized/rplexicon/ui/screens/spell/SpellDetailViewModel.kt +++ b/app/src/main/java/com/pixelized/rplexicon/ui/screens/spell/SpellDetailViewModel.kt @@ -26,7 +26,7 @@ class SpellDetailViewModel @Inject constructor( init { val argument = savedStateHandle.spellDetailArgument - val assignedSpell = repository.findAssignedSpell( + val assignedSpell = repository.find( character = argument.character, spell = argument.spell, )?.let { diff --git a/app/src/main/java/com/pixelized/rplexicon/utilitary/extentions/IntEx.kt b/app/src/main/java/com/pixelized/rplexicon/utilitary/extentions/IntEx.kt index 88fbdce..dd53d0d 100644 --- a/app/src/main/java/com/pixelized/rplexicon/utilitary/extentions/IntEx.kt +++ b/app/src/main/java/com/pixelized/rplexicon/utilitary/extentions/IntEx.kt @@ -28,7 +28,7 @@ val Int.signLabel: String fun List.toLabel(): String = when (size) { 0 -> "" 1 -> "${abs(this[0])}" - else -> joinToString(" ") { "${it.signLabel} ${abs(it)}" }.substring(2) + else -> filter { it != 0 }.joinToString(" ") { "${it.signLabel} ${abs(it)}" }.substring(2) } val Int.uri: String diff --git a/app/src/main/java/com/pixelized/rplexicon/utilitary/extentions/local/AlterationEx.kt b/app/src/main/java/com/pixelized/rplexicon/utilitary/extentions/local/AlterationEx.kt index 5801726..1409acc 100644 --- a/app/src/main/java/com/pixelized/rplexicon/utilitary/extentions/local/AlterationEx.kt +++ b/app/src/main/java/com/pixelized/rplexicon/utilitary/extentions/local/AlterationEx.kt @@ -3,14 +3,45 @@ package com.pixelized.rplexicon.utilitary.extentions.local import com.pixelized.rplexicon.model.Alteration import com.pixelized.rplexicon.model.Property -fun List.status(): Map> { +fun List.toStatus(): Map> { val status = hashMapOf>() - forEach { alteration -> - alteration.status.forEach { statut -> - status.getOrPut(statut.key) { mutableListOf() }.add(statut.value) + alteration.status.forEach { + status.getOrPut(it.key) { mutableListOf() }.add(it.value) } } - return status } + +fun List.toActiveStatus(): Map> { + val status = hashMapOf>() + forEach { alteration -> + if (alteration.active) { + alteration.status.forEach { + status.getOrPut(it.key) { mutableListOf() }.add(it.value) + } + } + } + return status +} + +val List?.sum: Int + get() = this?.sumOf { alt -> alt.bonus.sumOf { it.value } } ?: 0 + +val List?.advantage: Boolean + get() = this?.any { it.advantage } ?: false + +val List?.disadvantage: Boolean + get() = this?.any { it.disadvantage } ?: false + +val List?.fail: Boolean + get() = this?.any { it.fail } ?: false + +val Map>.isSavageAttacks: Boolean + get() = this[Property.PHYSICAL_MELEE_DAMAGE]?.any { it.name == "Attaques sauvages" } == true + +val Map>.isBrutalCritical: Boolean + get() = this[Property.PHYSICAL_MELEE_DAMAGE]?.any { it.name == "Critique brutal" } == true + +val Map>.isCritical: Boolean + get() = this[Property.PHYSICAL_MELEE_DAMAGE]?.any { it.name == "Critique" } == true diff --git a/app/src/main/java/com/pixelized/rplexicon/utilitary/extentions/local/PropertyEx.kt b/app/src/main/java/com/pixelized/rplexicon/utilitary/extentions/local/PropertyEx.kt deleted file mode 100644 index ebd87ac..0000000 --- a/app/src/main/java/com/pixelized/rplexicon/utilitary/extentions/local/PropertyEx.kt +++ /dev/null @@ -1,50 +0,0 @@ -package com.pixelized.rplexicon.utilitary.extentions.local - -import com.pixelized.rplexicon.model.CharacterSheet -import com.pixelized.rplexicon.model.Property -import com.pixelized.rplexicon.utilitary.extentions.modifier - -fun Property.toValue( - character: CharacterSheet, -): Int { - return when (this) { - Property.PROFICIENCY -> character.proficiency - Property.HIT_POINT -> 0 // TODO - Property.ARMOR_CLASS -> 0 // TODO - Property.INITIATIVE -> 0 // TODO - Property.STRENGTH -> character.strength.modifier // TODO have a dedicated modifier property. - Property.DEXTERITY -> character.dexterity.modifier // TODO have a dedicated modifier property. - Property.CONSTITUTION -> character.constitution.modifier // TODO have a dedicated modifier property. - Property.INTELLIGENCE -> character.intelligence.modifier // TODO have a dedicated modifier property. - Property.WISDOM -> character.wisdom.modifier // TODO have a dedicated modifier property. - Property.CHARISMA -> character.charisma.modifier // TODO have a dedicated modifier property. - Property.STRENGTH_SAVING_THROW -> character.strengthSavingThrows - Property.DEXTERITY_SAVING_THROW -> character.dexteritySavingThrows - Property.CONSTITUTION_SAVING_THROW -> character.constitutionSavingThrows - Property.INTELLIGENCE_SAVING_THROW -> character.intelligenceSavingThrows - Property.WISDOM_SAVING_THROW -> character.wisdomSavingThrows - Property.CHARISMA_SAVING_THROW -> character.charismaSavingThrows - Property.ACROBATICS -> character.acrobatics - Property.ANIMAL_HANDLING -> character.animalHandling - Property.ARCANA -> character.arcana - Property.ATHLETICS -> character.athletics - Property.DECEPTION -> character.deception - Property.HISTORY -> character.history - Property.INSIGHT -> character.insight - Property.INTIMIDATION -> character.intimidation - Property.INVESTIGATION -> character.investigation - Property.MEDICINE -> character.medicine - Property.NATURE -> character.nature - Property.PERCEPTION -> character.perception - Property.PERFORMANCE -> character.performance - Property.PERSUASION -> character.persuasion - Property.RELIGION -> character.religion - Property.SLEIGHT_OF_HAND -> character.sleightOfHand - Property.STEALTH -> character.stealth - Property.SURVIVAL -> character.survival - Property.PHYSICAL_MELEE_ATTACK -> 0 // TODO - Property.PHYSICAL_MELEE_DAMAGE -> 0 // TODO - Property.PHYSICAL_RANGE_ATTACK -> 0 // TODO - Property.PHYSICAL_RANGE_DAMAGE -> 0 // TODO - } -} \ No newline at end of file diff --git a/app/src/main/java/com/pixelized/rplexicon/utilitary/extentions/local/ThrowEx.kt b/app/src/main/java/com/pixelized/rplexicon/utilitary/extentions/local/ThrowEx.kt deleted file mode 100644 index 6f7dc7c..0000000 --- a/app/src/main/java/com/pixelized/rplexicon/utilitary/extentions/local/ThrowEx.kt +++ /dev/null @@ -1,20 +0,0 @@ -package com.pixelized.rplexicon.utilitary.extentions.local - -import com.pixelized.rplexicon.model.CharacterSheet -import com.pixelized.rplexicon.model.Throw - -/** - * Helper method to build a readable String from a Throw. - * Create a string following the format "amount 'd' faces '+' modifiers + amount * level 'd' faces" - * where modifier is a integer, sum of all modifiers values. - * ex : for Sleep level 2 -> 5d8+4 + 2d8 - */ -fun Throw.toString(character: CharacterSheet, level: Int = 0): String? { - val modifierLabel = modifier - .sumOf { modifier -> modifier.toValue(character = character) } - .let { if (it > 0) "+${it * level}" else "" } - return when (level > 0) { - true -> "${amount * level}d${faces}${modifierLabel}" - else -> null - } -} \ No newline at end of file diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml index fb28b4f..fb96f45 100644 --- a/app/src/main/res/values-fr/strings.xml +++ b/app/src/main/res/values-fr/strings.xml @@ -6,6 +6,9 @@ Une erreur s\'est produite. La structure du fichier semble avoir changé et n\'est plus compatible avec cette application. + SUCCÈS CRITIQUE + ÉCHEC CRITIQUE + Mâle Femelle Indéterminé @@ -122,9 +125,8 @@ Bonus de Maîtrise Bonus d\'Expertise - TEST \"%1$s\" + Test \"%1$s\" Lancer : \"%1$s\" - Test \"%1$s\" Bonus \"%1$s\" Jet d\'attaque : \"%1$s\" Jet de dommage : \"%1$s\" @@ -133,7 +135,7 @@ Critique Alterations - JET DE SAUVEGARDE : %1$s + Jet de sauvegarde : %1$s Sauvegarde de \"%1$s\" Attaque de corps à corps diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 5182c63..2a9b12a 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -6,6 +6,9 @@ An error occur. The file structure appears to have changed and is no longer compatible with this application + CRITICAL SUCCESS + CRITICAL FAILURE + Male Female Undetermined @@ -123,17 +126,16 @@ Proficiency bonus Expertise bonus Cast %1$s - %1$s CHECK - %1$s check + %1$s check %1$s bonus - %1$s HIT - %1$s DAMAGE - %1$s HIT - %1$s DAMAGE + %1$s hit + %1$s damage + %1$s hit + %1$s damage Critical Altérations - %1$s SAVING THROW + %1$s saving throw %1$s save Melee physical attack