Alteration rework.

This commit is contained in:
Thomas Andres Gomez 2023-09-29 15:36:04 +02:00
parent c5874fcc8c
commit e01aa0893c
36 changed files with 1777 additions and 1088 deletions

View file

@ -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<String>): 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<Property, List<Alteration.Status>>,
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<Property, List<Alteration.Status>>,
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<Property, List<Alteration.Status>>,
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<Int>()
// 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<Property, List<Alteration.Status>>,
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<Int>()
// 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<Property, List<Alteration.Status>>,
): 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<Int>()
// 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,
)
}