1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
use rand::Rng;

use crate::OutcomePart;
use crate::RollExpression;
use crate::{CheckOutcome, CritScore};
use crate::{Score, Sides};

mod damageoutcome;
mod damageparse;

pub use damageoutcome::{DamageOutcome, DamageOutcomeBuilder};

// TODO: DamagePart should not be `pub` (once Check stops using Damage)
#[derive(PartialEq, Debug, Clone)]
pub enum DamagePart {
    Dice(u32, Sides),
    Modifier(Score),
}

/// A list of dice to roll and modifiers to add, usually used for
/// damage.
///
/// ```
/// use critfail::{RollExpression, Damage};
///
/// let damage = Damage::new("2d8+1").unwrap();
///
/// let outcome = damage.roll();
///
/// print!("{}", outcome);   // Prints something like "11"
/// print!("{:?}", outcome); // Prints something like "[2+5]+4"
/// ```
#[derive(Debug, PartialEq, Clone)]
pub struct Damage(pub Vec<DamagePart>);

impl Damage {
    /// Roll this `Damage` as though it were a critical hit.
    ///
    /// This rolls all the positive dice in the `Damage` twice.
    /// Modifiers and dice with negative values are only counted once.
    ///
    /// ```
    /// use critfail::{RollExpression, Damage};
    ///
    /// let damage = Damage::new("2d8+3-1d4").unwrap();
    /// let outcome = damage.crit_roll();
    /// // The d8s are rolled twice, the +3 is counted once, and the d4 is rolled once
    /// print!("{:?}", damage)  // Prints something like "[5+6+4+7]+3-[2]"
    /// ```
    pub fn crit_roll(&self) -> DamageOutcome {
        let mut result = Vec::new();

        for part in &self.0 {
            match part {
                DamagePart::Dice(_, sides) if *sides > 0 => {
                    result.push(part.roll());
                    result.push(part.roll());
                }
                _ => {
                    result.push(part.roll());
                }
            }
        }

        DamageOutcome::new(result)
    }

    /// Roll for damage, doubling if the check was a critical success.
    pub fn roll_with_check(&self, check: &CheckOutcome) -> DamageOutcome {
        self.roll_with_score(check.crit_score())
    }

    /// Roll for damage, doubling if the check was a critical success.
    pub fn roll_with_score(&self, score: CritScore) -> DamageOutcome {
        match score {
            CritScore::Critical => self.crit_roll(),
            _ => self.roll(),
        }
    }
}

impl RollExpression for Damage {
    type Outcome = DamageOutcome;

    fn roll(&self) -> Self::Outcome {
        DamageOutcome::new(self.0.iter().map(|part| part.roll()).collect())
    }
}

impl RollExpression for DamagePart {
    type Outcome = OutcomePart;

    fn roll(&self) -> Self::Outcome {
        match self {
            DamagePart::Dice(num, sides) => {
                let rolls: Vec<Score> = (0..*num)
                    .map(|_| rand::thread_rng().gen_range(1, sides.abs() + 1))
                    .collect();

                OutcomePart::Dice(*sides, rolls)
            }

            DamagePart::Modifier(value) => OutcomePart::Modifier(*value),
        }
    }
}