package com.pixelized.rplexicon.business import android.app.Application import android.content.Context import com.pixelized.rplexicon.R import com.pixelized.rplexicon.data.model.Alteration import com.pixelized.rplexicon.data.model.AssignedSpell import com.pixelized.rplexicon.data.model.CharacterSheet import com.pixelized.rplexicon.data.model.DiceThrow import com.pixelized.rplexicon.data.model.Property import com.pixelized.rplexicon.data.model.Skill import com.pixelized.rplexicon.data.model.Throw import com.pixelized.rplexicon.data.repository.character.ActionRepository import com.pixelized.rplexicon.data.repository.character.AlterationRepository import com.pixelized.rplexicon.data.repository.character.CharacterSheetRepository import com.pixelized.rplexicon.data.repository.character.ObjectActionRepository import com.pixelized.rplexicon.data.repository.character.SkillRepository import com.pixelized.rplexicon.data.repository.character.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.critical 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 objectRepository: ObjectActionRepository, private val spellRepository: SpellRepository, private val skillRepository: SkillRepository, 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) } 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.DeathSavingThrow -> savingThrow( character = sheet, alterations = alterations, abilityLabel = { getString(R.string.character_sheet_stat_death) }, relatedLabel = { getString(R.string.character_sheet_stat_death) }, ability = Property.DEATH_SAVING_THROW, relatedStat = null, ) is DiceThrow.Acrobatics -> abilityThrow( character = sheet, alterations = alterations, 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.PERCEPTION, 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, ) val objectAlterations = alterationRepository.getAlterations( objects = action?.alterations, ) actionThrow( character = sheet, action = diceThrow.weapon, diceThrow = action?.hit, title = { getString(R.string.dice_roll_attack_hit_title, it) }, alterations = alterations + objectAlterations, ability = Property.PHYSICAL_MELEE_ATTACK, ) } is DiceThrow.PhysicalMeleeDamage -> { val action = actionRepository.find( character = diceThrow.character, action = diceThrow.weapon, ) val objectAlterations = alterationRepository.getAlterations( objects = action?.alterations, ) actionThrow( character = sheet, action = diceThrow.weapon, diceThrow = action?.damage, title = { getString(R.string.dice_roll_attack_damage_title, it) }, alterations = alterations + objectAlterations, ability = Property.PHYSICAL_MELEE_DAMAGE, ) } is DiceThrow.PhysicalRangeAttack -> { val action = actionRepository.find( character = diceThrow.character, action = diceThrow.weapon, ) val objectAlterations = alterationRepository.getAlterations( objects = action?.alterations, ) actionThrow( character = sheet, action = diceThrow.weapon, diceThrow = action?.hit, title = { getString(R.string.dice_roll_attack_hit_title, it) }, alterations = alterations + objectAlterations, ability = Property.PHYSICAL_RANGE_ATTACK, ) } is DiceThrow.PhysicalRangeDamage -> { val action = actionRepository.find( character = diceThrow.character, action = diceThrow.weapon, ) val objectAlterations = alterationRepository.getAlterations( objects = action?.alterations, ) actionThrow( character = sheet, action = diceThrow.weapon, diceThrow = action?.damage, title = { getString(R.string.dice_roll_attack_damage_title, it) }, alterations = alterations + objectAlterations, ability = Property.PHYSICAL_RANGE_DAMAGE, ) } is DiceThrow.Object -> { val action = objectRepository.find( character = diceThrow.character, item = diceThrow.item, ) actionThrow( character = sheet, action = diceThrow.item, diceThrow = action?.effect, title = { getString(R.string.dice_roll_use_object, it) }, alterations = alterations, ability = Property.OBJECT_EFFECT, ) } is DiceThrow.SpellAttack -> { val spell = spellRepository.findAssignedSpell( 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.findAssignedSpell( 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.findAssignedSpell( character = diceThrow.character, spell = diceThrow.spell, ) spellThrow( character = sheet, spell = spell, level = diceThrow.level, alterations = alterations, ) } is DiceThrow.Skill -> { val skill = skillRepository.find( character = diceThrow.character, skill = diceThrow.skill, ) skillThrow( character = sheet, alterations = alterations, skill = skill, ) } } } else { null } } private fun abilityThrow( character: CharacterSheet, alterations: List, 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: List, 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: List, abilityTitle: Context.(label: String) -> String, abilityLabel: Context.() -> String, relatedTitle: Context.(label: String) -> String?, relatedLabel: Context.() -> String?, ability: Property, relatedStat: Property?, ): DiceThrowResult { with(ThrowScope(context = application, character = character, alterations = alterations)) { // retrieve some wording. val abilityLabelString = abilityLabel(application) val abilityTitleString = abilityTitle(application, abilityLabelString) // check if the roll is affected by some status. val advantage = status[ability].advantage val disadvantage = status[ability].disadvantage // main roll val result = roll( advantage = advantage, disadvantage = disadvantage, fail = status[ability].fail, critical = status[ability].critical, ) // fetch and build a list of dice roll base on alterations. val diceAlterationBonus = status[ability].alterationsBonus() // fetch and build a list of flat bonus val flatAlterationBonus = status[ability].flatAlterationBonus() // fetch and build the associated characteristic val relatedStatBonus = when (relatedStat) { Property.STRENGTH -> (character.strength + status[relatedStat].sum).modifier Property.DEXTERITY -> (character.dexterity + status[relatedStat].sum).modifier Property.CONSTITUTION -> (character.constitution + status[relatedStat].sum).modifier Property.INTELLIGENCE -> (character.intelligence + status[relatedStat].sum).modifier Property.WISDOM -> (character.wisdom + status[relatedStat].sum).modifier Property.CHARISMA -> (character.charisma + status[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() // compute the final roll result val rollResult = when (result.value) { 1, 20 -> "${result.value}" else -> "${allValue.sum()}" } // build the result. return DiceThrowResult( dice = RollDiceUio( isCriticalSuccess = result.value == 20, isCriticalFailure = result.value == 1, result = rollResult, ), card = ThrowsCardUio( title = abilityTitleString.uppercase(), highlight = abilityLabelString.uppercase(), dice = R.drawable.ic_d20_24, roll = allValue.toLabel(), result = rollResult, 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: List, ability: Property?, ): DiceThrowResult { with(ThrowScope(context = application, character = character, alterations = alterations)) { // retrieve some wording. val titleString = title(application, action) // check if this throw can be a critical success of failure. val canMakeCriticalRoll = when (ability) { Property.PHYSICAL_MELEE_ATTACK -> true Property.PHYSICAL_RANGE_ATTACK -> true Property.SPELL_ATTACK -> true else -> false } // check if the roll is affected by some status. val advantage = status[ability].advantage val disadvantage = status[ability].disadvantage // compute the amount of main dice to throw. val amount = if (status.isCritical) { diceThrow?.amount?.times(2)?.let { if (ability == Property.PHYSICAL_MELEE_DAMAGE && status.isSavageAttacks) it.plus(1) else it }?.let { if (ability == Property.PHYSICAL_MELEE_DAMAGE && status.isBrutalCritical) it.plus(1) else it } } else { diceThrow?.amount } // main roll val result = roll( amount = amount ?: 1, faces = diceThrow?.faces ?: 20, advantage = advantage, disadvantage = disadvantage, fail = status[ability].fail, critical = status[ability].critical, ) // fetch and build a list of dice roll base on alterations. val diceAlterationBonus = status[ability].alterationsBonus() // fetch and build a list of flat bonus val flatAlterationBonus = status[ability].flatAlterationBonus() // fetch and build the associated characteristic val relatedStatBonus = diceThrow.statBonus(name = action) // check for flat dice bonus (ex: healing potion 2d4 + 2) val flatBonus = diceThrow?.flat?.takeIf { it > 0 }?.let { allValue.add(it) listOf( ThrowsCardUio.Detail( title = application.getString(R.string.dice_roll_bonus_detail, action), result = "$it", ) ) } ?: emptyList() // compute the final roll result val rollResult = when { canMakeCriticalRoll && result.value == 20 -> "20" canMakeCriticalRoll && result.value == 1 -> "1" else -> "${allValue.sum()}" } // 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 = rollResult, ), 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 = rollResult, 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 + flatBonus, ) ) } } private fun spellThrow( character: CharacterSheet, spell: AssignedSpell?, level: Int, alterations: List, ): DiceThrowResult { with(ThrowScope(context = application, character = character, alterations = alterations)) { // retrieve some wording. val spellName = spell?.spell?.name ?: "" val titleString = application.getString(R.string.dice_roll_spell_cast, spellName) val advantage = status[Property.SPELL_EFFECT].advantage val disadvantage = status[Property.SPELL_EFFECT].disadvantage // main roll val result = roll( amount = spell?.effect?.amount ?: 1, faces = spell?.effect?.faces ?: 4, advantage = advantage, disadvantage = disadvantage, fail = status[Property.SPELL_EFFECT].fail, critical = status[Property.SPELL_EFFECT].critical, ) // 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, advantage = advantage, disadvantage = disadvantage, fail = status[Property.SPELL_EFFECT].fail, critical = status[Property.SPELL_EFFECT].critical, ) 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}", advantage = advantage, disadvantage = disadvantage, result = localRoll.label, ), result = "${localRoll.value}", ) } } else { emptyList() } // fetch and build the associated characteristic val relatedStatBonus = spell?.effect.statBonus(name = spellName) // compute the final roll result val rollResult = "${allValue.sum()}" // build the result. return DiceThrowResult( dice = RollDiceUio( icon = (spell?.effect?.faces ?: 4).icon, result = rollResult, ), card = ThrowsCardUio( title = titleString.uppercase(), highlight = spellName, dice = (spell?.effect?.faces ?: 4).icon, roll = allValue.toLabel(), result = rollResult, 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}", advantage = advantage, disadvantage = disadvantage, result = result.label, ), result = "${result.value}", ), ) + levelBonus + relatedStatBonus, ) ) } } private fun skillThrow( character: CharacterSheet, alterations: List, skill: Skill?, ): DiceThrowResult { with(ThrowScope(context = application, character = character, alterations = alterations)) { // retrieve some wording. val spellName = skill?.name val titleString = application.getString(R.string.dice_roll_spell_cast, spellName) val advantage = status[Property.SKILL].advantage val disadvantage = status[Property.SKILL].disadvantage // main roll val result = roll( amount = skill?.effect?.amount ?: 1, faces = skill?.effect?.faces ?: 4, advantage = advantage, disadvantage = disadvantage, fail = status[Property.SKILL].fail, critical = status[Property.SKILL].critical, ) // fetch and build a list of dice roll base on alterations. val diceAlterationBonus = status[Property.SKILL].alterationsBonus() // fetch and build a list of flat bonus val flatAlterationBonus = status[Property.SKILL].flatAlterationBonus() // fetch and build the associated characteristic, proficiency or level val relatedStatBonus = skill?.effect.statBonus(name = skill?.name) // compute the final roll result val rollResult = "${allValue.sum()}" // build the result. return DiceThrowResult( dice = RollDiceUio( icon = (skill?.effect?.faces ?: 4).icon, result = rollResult, ), card = ThrowsCardUio( title = titleString.uppercase(), highlight = spellName, dice = (skill?.effect?.faces ?: 4).icon, roll = allValue.toLabel(), result = rollResult, details = listOf( ThrowsCardUio.Detail( title = spellName, throws = ThrowsCardUio.Throw( dice = (skill?.effect?.faces ?: 4).icon, roll = "${skill?.effect?.amount ?: 1}d${skill?.effect?.faces ?: 4}", advantage = advantage, disadvantage = disadvantage, result = result.label, ), result = "${result.value}", ), ) + diceAlterationBonus + flatAlterationBonus + relatedStatBonus, ) ) } } /** * Helper class to track rolls and declare helper method. */ private class ThrowScope( val context: Context, val allValue: MutableList = mutableListOf(), val character: CharacterSheet, val alterations: List, ) { val status = this.alterations.toStatus() /** * Fetch any stats / proficiency / level related bonus and build a ThrowsCardUio.Detail for each. */ fun Throw?.statBonus(name: String?): List { return this?.modifier?.mapNotNull { it.statBonus(name = name) } ?: emptyList() } fun Property.statBonus(name: String?): ThrowsCardUio.Detail? { return when (this) { Property.STRENGTH -> (character.strength + this@ThrowScope.status[this].sum).modifier Property.DEXTERITY -> (character.dexterity + this@ThrowScope.status[this].sum).modifier Property.CONSTITUTION -> (character.constitution + this@ThrowScope.status[this].sum).modifier Property.INTELLIGENCE -> (character.intelligence + this@ThrowScope.status[this].sum).modifier Property.WISDOM -> (character.wisdom + this@ThrowScope.status[this].sum).modifier Property.CHARISMA -> (character.charisma + this@ThrowScope.status[this].sum).modifier Property.PROFICIENCY -> character.proficiency Property.LEVEL -> character.level else -> null }?.let { value -> val titleLabel = if (this == Property.PROFICIENCY) { context.getString(R.string.dice_roll_mastery_proficiency, name) } else { val label = when (this) { Property.STRENGTH -> context.getString(R.string.character_sheet_stat_strength) Property.DEXTERITY -> context.getString(R.string.character_sheet_stat_dexterity) Property.CONSTITUTION -> context.getString(R.string.character_sheet_stat_constitution) Property.INTELLIGENCE -> context.getString(R.string.character_sheet_stat_intelligence) Property.WISDOM -> context.getString(R.string.character_sheet_stat_wisdom) Property.CHARISMA -> context.getString(R.string.character_sheet_stat_charisma) Property.LEVEL -> context.getString(R.string.character_sheet_stat_level) else -> "" } context.getString(R.string.dice_roll_bonus_detail, label) } allValue.add(value) ThrowsCardUio.Detail( title = titleLabel, result = "$value", ) } } /** * Fetch any alteration related bonus and build a ThrowsCardUio.Detail for each. */ fun List?.alterationsBonus(): List { return this?.flatMap { status -> status.dices.map { dice -> val localRoll = roll( amount = if (this@ThrowScope.status.isCritical) dice.count * 2 else dice.count, faces = dice.faces, advantage = dice.advantage, disadvantage = dice.disadvantage, fail = status.fail, critical = status.critical, ) 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 any flat number related bonus and build a ThrowsCardUio.Detail for each. */ fun List?.flatAlterationBonus(): List { return this?.flatMap { status -> status.bonus.map { bonus -> allValue.add(bonus.value) ThrowsCardUio.Detail( title = bonus.title, result = "${bonus.value}", ) } } ?: emptyList() } /** * Make a roll of dices. */ fun roll( amount: Int = 1, faces: Int = 20, advantage: Boolean = false, disadvantage: Boolean = false, fail: Boolean = false, critical: Boolean = false, ): DiceRollResult { val result = when { advantage && !disadvantage -> { val roll = List(amount) { val first = random(faces = faces, fail = fail, critical = critical) val second = random(faces = faces, fail = fail, critical = critical) first to second } DiceRollResult( label = roll.joinToString(" + ") { "${it.first}~${it.second}" }, value = roll.sumOf { max(it.first, it.second) }, ) } disadvantage && !advantage -> { val roll = List(amount) { val first = random(faces = faces, fail = fail, critical = critical) val second = random(faces = faces, fail = fail, critical = critical) first to second } DiceRollResult( label = roll.joinToString(" + ") { "${it.first}~${it.second}" }, value = roll.sumOf { min(it.first, it.second) }, ) } else -> { val roll = List(amount) { random(faces = faces, fail = fail, critical = critical) } DiceRollResult( label = roll.toLabel(), value = roll.sum(), ) } } allValue.add(result.value) return result } private fun random(faces: Int, fail: Boolean, critical: Boolean): Int = when { fail && !critical -> 1 critical && !fail -> faces else -> (Math.random() * faces + 1).toInt() } } private data class DiceRollResult( val label: String, val value: Int, ) }