package com.pixelized.rplexicon.business import android.app.Application import android.content.Context import com.pixelized.rplexicon.R import com.pixelized.rplexicon.data.model.AssignedSpell import com.pixelized.rplexicon.data.model.CharacterSheet import com.pixelized.rplexicon.data.model.DiceThrow import com.pixelized.rplexicon.data.model.Property import com.pixelized.rplexicon.data.model.Skill import com.pixelized.rplexicon.data.model.alteration.Alteration import com.pixelized.rplexicon.data.model.roll.Flat import com.pixelized.rplexicon.data.model.roll.Throw import com.pixelized.rplexicon.data.network.NetworkThrow 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.utilitary.extentions.icon import com.pixelized.rplexicon.utilitary.extentions.local.advantage import com.pixelized.rplexicon.utilitary.extentions.local.base import com.pixelized.rplexicon.utilitary.extentions.local.critical import com.pixelized.rplexicon.utilitary.extentions.local.disadvantage import com.pixelized.rplexicon.utilitary.extentions.local.emphasis 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.primary import com.pixelized.rplexicon.utilitary.extentions.local.secondary import com.pixelized.rplexicon.utilitary.extentions.local.sum import com.pixelized.rplexicon.utilitary.extentions.local.tertiary import com.pixelized.rplexicon.utilitary.extentions.local.toStatus import com.pixelized.rplexicon.utilitary.extentions.masteryMultiplier import com.pixelized.rplexicon.utilitary.extentions.modifier import com.pixelized.rplexicon.utilitary.extentions.toLabel import kotlinx.coroutines.flow.firstOrNull import javax.inject.Inject import kotlin.math.abs import kotlin.math.max import kotlin.math.min import kotlin.random.Random data class DiceThrowResult( val dice: RollDiceUio, val throws: NetworkThrow, ) @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, ) { suspend fun roll( diceThrow: DiceThrow, isThrowHidden: Boolean, alterationId: List, ): DiceThrowResult? { val sheet = characterSheetRepository.find(name = diceThrow.character) val alterations = alterationRepository.getAssignedAlterations(diceThrow.character).firstOrNull() ?.filter { alterationId.contains(it.name) } ?: emptyList() 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, isThrowHidden = isThrowHidden, ) 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, isThrowHidden = isThrowHidden, ) 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, isThrowHidden = isThrowHidden, ) 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, isThrowHidden = isThrowHidden, ) 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, isThrowHidden = isThrowHidden, ) 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, isThrowHidden = isThrowHidden, ) 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, isThrowHidden = isThrowHidden, ) 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, isThrowHidden = isThrowHidden, ) 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, isThrowHidden = isThrowHidden, ) 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, isThrowHidden = isThrowHidden, ) 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, isThrowHidden = isThrowHidden, ) 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, isThrowHidden = isThrowHidden, ) 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, isThrowHidden = isThrowHidden, ) 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, isThrowHidden = isThrowHidden, ) 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, isThrowHidden = isThrowHidden, ) 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, isThrowHidden = isThrowHidden, ) 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, isThrowHidden = isThrowHidden, ) 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, isThrowHidden = isThrowHidden, ) 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, isThrowHidden = isThrowHidden, ) 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, isThrowHidden = isThrowHidden, ) 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, isThrowHidden = isThrowHidden, ) 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, isThrowHidden = isThrowHidden, ) 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, isThrowHidden = isThrowHidden, ) 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, isThrowHidden = isThrowHidden, ) 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, isThrowHidden = isThrowHidden, ) 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, isThrowHidden = isThrowHidden, ) 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, isThrowHidden = isThrowHidden, ) 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, isThrowHidden = isThrowHidden, ) 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, isThrowHidden = isThrowHidden, ) 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, isThrowHidden = isThrowHidden, ) 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, isThrowHidden = isThrowHidden, ) 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, isThrowHidden = isThrowHidden, ) 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_damage_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_damage_title, it) }, alterations = alterations, 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, isThrowHidden: Boolean, ): 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, isThrowHidden = isThrowHidden, ) private fun savingThrow( character: CharacterSheet, alterations: List, abilityLabel: Context.() -> String, relatedLabel: Context.() -> String?, ability: Property, relatedStat: Property?, isThrowHidden: Boolean, ): 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, isThrowHidden = isThrowHidden, ) 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?, isThrowHidden: Boolean, ): 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 val emphasis = status[ability].emphasis // main roll val result = roll( advantage = advantage, disadvantage = disadvantage, emphasis = emphasis, 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( NetworkThrow.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 { val multiplier = it.masteryMultiplier(status = status[ability]) val mastery = character.proficiency * multiplier allValue.add(mastery) listOf( NetworkThrow.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, ), throws = NetworkThrow( timestamp = System.currentTimeMillis(), title = abilityTitleString.uppercase(), highlight = abilityLabelString.uppercase(), hidden = isThrowHidden, face = 20, roll = allValue.toLabel(), result = rollResult, isCriticalSuccess = result.value == 20, isCriticalFailure = result.value == 1, details = listOf( NetworkThrow.Detail( title = abilityTitleString, throws = NetworkThrow.Throw( dice = R.drawable.ic_d20_24, advantage = advantage, disadvantage = disadvantage, emphasis = emphasis, 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 val emphasis = status[ability].emphasis // compute the amount of main dice to throw. val amount = if (status.isCritical) { diceThrow?.dice?.count?.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?.dice?.count } // main roll val result = roll( amount = amount ?: 1, faces = diceThrow?.dice?.faces ?: 20, advantage = advantage, disadvantage = disadvantage, emphasis = emphasis, 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 relatedFlatBonus = diceThrow.flatBonus(name = action) // 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?.dice?.icon ?: R.drawable.ic_d20_24, isCriticalSuccess = canMakeCriticalRoll && result.value == 20, isCriticalFailure = canMakeCriticalRoll && result.value == 1, result = rollResult, ), throws = NetworkThrow( timestamp = System.currentTimeMillis(), title = titleString.uppercase(), highlight = action.uppercase(), face = diceThrow?.dice?.faces ?: 20, isCriticalSuccess = canMakeCriticalRoll && result.value == 20, isCriticalFailure = canMakeCriticalRoll && result.value == 1, roll = allValue.toLabel(), result = rollResult, details = listOf( NetworkThrow.Detail( title = action, throws = NetworkThrow.Throw( dice = diceThrow?.dice?.icon ?: R.drawable.ic_d20_24, advantage = advantage, disadvantage = disadvantage, emphasis = emphasis, roll = "${diceThrow?.dice?.count}d${diceThrow?.dice?.faces}", result = result.label, ), result = "${result.value}", ), ) + diceAlterationBonus + flatAlterationBonus + relatedStatBonus + relatedFlatBonus, ), ) } } 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 val emphasis = status[Property.SPELL_EFFECT].emphasis // main roll val result = roll( amount = spell?.effect?.dice?.count ?: 1, faces = spell?.effect?.dice?.faces ?: 4, advantage = advantage, disadvantage = disadvantage, emphasis = emphasis, fail = status[Property.SPELL_EFFECT].fail, critical = status[Property.SPELL_EFFECT].critical, ) // fetch and build a list of additional level effect. val levelBonus = if (spell?.level?.dice != null) { ((spell.spell.level + 1)..level).map { val localRoll = roll( amount = spell.level.dice.count, faces = spell.level.dice.faces, advantage = advantage, disadvantage = disadvantage, emphasis = emphasis, fail = status[Property.SPELL_EFFECT].fail, critical = status[Property.SPELL_EFFECT].critical, ) NetworkThrow.Detail( title = application.getString(R.string.spell_level_chooser_label, "$it"), throws = NetworkThrow.Throw( dice = spell.level.dice.icon, roll = spell.level.dice.toLabel(), advantage = advantage, disadvantage = disadvantage, emphasis = emphasis, result = localRoll.label, ), result = "${localRoll.value}", ) } } else { emptyList() } // fetch and build the associated characteristic val relatedStatBonus = spell?.effect.statBonus(name = spellName) // fetch and build a list of flat bonus val relatedFlatBonus = spell?.effect?.flatBonus(name = spellName) ?: emptyList() // compute the final roll result val rollResult = "${allValue.sum()}" // build the result. return DiceThrowResult( dice = RollDiceUio( icon = spell?.effect?.dice?.icon ?: R.drawable.ic_d4_24, result = rollResult, ), throws = NetworkThrow( timestamp = System.currentTimeMillis(), title = titleString.uppercase(), highlight = spellName.uppercase(), face = spell?.effect?.dice?.faces ?: 4, roll = allValue.toLabel(), result = rollResult, details = listOf( NetworkThrow.Detail( title = spellName, throws = NetworkThrow.Throw( dice = spell?.effect?.dice?.icon ?: R.drawable.ic_d4_24, roll = spell?.effect?.dice?.toLabel() ?: "1d4", advantage = advantage, disadvantage = disadvantage, emphasis = emphasis, result = result.label, ), result = "${result.value}", ), ) + levelBonus + relatedStatBonus + relatedFlatBonus, ), ) } } 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 val emphasis = status[Property.SKILL].emphasis // main roll val result = roll( amount = skill?.effect?.dice?.count ?: 1, faces = skill?.effect?.dice?.faces ?: 4, advantage = advantage, disadvantage = disadvantage, emphasis = emphasis, 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) // fetch and build a list of flat bonus val relatedFlatBonus = skill?.effect.flatBonus(name = skill?.name) // compute the final roll result val rollResult = "${allValue.sum()}" // build the result. return DiceThrowResult( dice = RollDiceUio( icon = skill?.effect?.dice?.icon ?: R.drawable.ic_d4_24, result = rollResult, ), throws = NetworkThrow( timestamp = System.currentTimeMillis(), title = titleString.uppercase(), highlight = spellName?.uppercase() ?: "", face = skill?.effect?.dice?.faces ?: 4, roll = allValue.toLabel(), result = rollResult, details = listOf( NetworkThrow.Detail( title = spellName ?: "", throws = NetworkThrow.Throw( dice = skill?.effect?.dice?.icon ?: R.drawable.ic_d4_24, roll = skill?.effect?.dice?.toLabel() ?: "1d4", advantage = advantage, disadvantage = disadvantage, emphasis = emphasis, result = result.label, ), result = "${result.value}", ), ) + diceAlterationBonus + flatAlterationBonus + relatedStatBonus + relatedFlatBonus, ), ) } } /** * 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() } /** * Fetch any flat bonus related to the throw. */ fun Throw?.flatBonus(name: String?): List { return this?.flat?.flatBonus(name)?.let { listOf(it) } ?: emptyList() } private fun Property.statBonus(name: String?): NetworkThrow.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.base Property.LEVEL_PC -> character.level.primary Property.LEVEL_SC -> character.level.secondary Property.LEVEL_TC -> character.level.tertiary 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) Property.LEVEL_PC -> context.getString(R.string.character_sheet_stat_level_pc) Property.LEVEL_SC -> context.getString(R.string.character_sheet_stat_level_sc) Property.LEVEL_TC -> context.getString(R.string.character_sheet_stat_level_tc) else -> "" } context.getString(R.string.dice_roll_bonus_detail, label) } allValue.add(value) NetworkThrow.Detail( title = titleLabel, result = "$value", ) } } private fun Flat.flatBonus(name: String?): NetworkThrow.Detail? { return when { value != 0 -> { allValue.add(value) NetworkThrow.Detail( title = context.getString(R.string.dice_roll_bonus_detail, name), result = "$value", ) } else -> null } } /** * 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, emphasis = dice.emphasis, fail = status.fail, critical = status.critical, ) NetworkThrow.Detail( title = status.name, throws = NetworkThrow.Throw( dice = dice.icon, advantage = dice.advantage, disadvantage = dice.disadvantage, emphasis = dice.emphasis, 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?.mapNotNull { status -> status.bonus?.let { bonus -> allValue.add(bonus.value) NetworkThrow.Detail( title = status.name, result = "${bonus.value}", ) } } ?: emptyList() } /** * Make a roll of dices. * @param amount the number of dice. * @param faces the number of faces on the dices. * @param advantage does the throws have advantage (roll 2 keep max) * @param disadvantage does the throws have disadvantage (roll 2 keep min) * @param emphasis does the throw have emphasis (roll 2, keep furthest away from 10) * @param fail force the result to be 1 * @param critical force the result to amount. */ fun roll( amount: Int = 1, faces: Int = 20, advantage: Boolean = false, disadvantage: Boolean = false, emphasis: Boolean, 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) }, ) } !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 { min(it.first, it.second) }, ) } !advantage && !disadvantage && emphasis -> { val roll = List(amount) { var first: Int var second: Int do { first = random(faces = faces, fail = fail, critical = critical) second = random(faces = faces, fail = fail, critical = critical) } while (abs(first - 10) == abs(second - 10)) if (abs(first - 10) < abs(second - 10)) { second to first } else { first to second } } DiceRollResult( label = roll.joinToString(" + ") { "${it.first}~${it.second}" }, value = roll.sumOf { it.first }, ) } 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 -> Random.nextInt(from = 1, until = faces + 1) } } private data class DiceRollResult( val label: String, val value: Int, ) }