arelith/item/
damage.rs

1use crate::dice::Dice;
2use serde::{Deserialize, Serialize};
3use std::{cell::RefCell, collections::HashMap};
4
5#[derive(Clone, Copy, Eq, PartialEq, Debug, Hash, Ord, PartialOrd, Serialize, Deserialize)]
6#[allow(unused)]
7pub enum DamageType {
8    Slashing,
9    Piercing,
10    Bludgeoning,
11    Magical,
12    Acid,
13    Cold,
14    Divine,
15    Electrical,
16    Fire,
17    Negative,
18    Positive,
19    Sonic,
20    Entropy,
21    Force,
22    Psychic,
23    Poison,
24    Unknown,
25}
26
27impl DamageType {
28    pub fn name(&self) -> String {
29        format!("{:?}", self)
30    }
31
32    pub fn is_physical(&self) -> bool {
33        match self {
34            Self::Slashing | Self::Piercing | Self::Bludgeoning => true,
35            _ => false,
36        }
37    }
38}
39
40impl From<&str> for DamageType {
41    fn from(value: &str) -> Self {
42        match value.to_lowercase().as_str() {
43            "slashing" => Self::Slashing,
44            "piercing" => Self::Piercing,
45            "bludgeoning" => Self::Bludgeoning,
46            "magical" => Self::Magical,
47            "acid" => Self::Acid,
48            "cold" => Self::Cold,
49            "divine" => Self::Divine,
50            "electrical" => Self::Electrical,
51            "fire" => Self::Fire,
52            "negative" => Self::Negative,
53            "positive" => Self::Positive,
54            "sonic" => Self::Sonic,
55            "entropy" => Self::Entropy,
56            "force" => Self::Force,
57            "psychic" => Self::Psychic,
58            "poison" => Self::Poison,
59            _ => Self::Unknown,
60        }
61    }
62}
63
64impl std::fmt::Display for DamageType {
65    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
66        write!(f, "{}", self.name())
67    }
68}
69
70#[derive(PartialEq, Serialize, Deserialize)]
71pub struct Damage {
72    amount: Dice,
73    pub type_: DamageType,
74    pub is_resistable: bool,
75    pub can_crit: bool,
76}
77
78impl std::fmt::Display for Damage {
79    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
80        write!(f, "{}", self.amount.to_string())
81    }
82}
83
84impl Damage {
85    pub fn new(type_: DamageType, amount: Dice, is_resistable: bool, can_crit: bool) -> Self {
86        Damage {
87            type_,
88            amount,
89            is_resistable,
90            can_crit,
91        }
92    }
93
94    #[allow(unused)]
95    pub fn roll(&self) -> i32 {
96        self.amount.roll()
97    }
98
99    pub fn roll_m(&self, count: i32) -> i32 {
100        self.amount.roll_m(count)
101    }
102}
103
104#[derive(Clone, Default, Debug, Serialize, Deserialize)]
105pub struct DamageResult(RefCell<HashMap<DamageType, i32>>);
106
107impl DamageResult {
108    pub fn new() -> Self {
109        Self::default()
110    }
111
112    #[allow(unused)]
113    pub fn set(&self, type_: DamageType, amount: i32) {
114        *self.0.borrow_mut().entry(type_).or_insert(0) += amount;
115    }
116
117    pub fn get(&self, type_: DamageType) -> i32 {
118        self.0.borrow().get(&type_).unwrap_or(&0).to_owned()
119    }
120
121    pub fn get_types(&self) -> Vec<DamageType> {
122        self.0.borrow().keys().cloned().collect::<Vec<DamageType>>()
123    }
124
125    pub fn get_types_sorted(&self) -> Vec<DamageType> {
126        let mut types = self.get_types();
127        types.sort();
128
129        types
130    }
131
132    pub fn add(&self, type_: DamageType, amount: i32) -> i32 {
133        let mut hashmap = self.0.borrow_mut();
134        let current_dmg = hashmap.entry(type_).or_insert(0);
135        *current_dmg += amount;
136
137        *current_dmg
138    }
139
140    pub fn sub(&self, type_: DamageType, amount: i32) -> i32 {
141        let mut hashmap = self.0.borrow_mut();
142
143        if hashmap.contains_key(&type_) {
144            let current_dmg = hashmap.get_mut(&type_);
145
146            if let Some(current_dmg) = current_dmg {
147                *current_dmg -= amount;
148
149                if *current_dmg < 0 {
150                    *current_dmg = 0;
151                }
152
153                return *current_dmg;
154            }
155        }
156
157        0
158    }
159
160    pub fn total_dmg(&self) -> i32 {
161        self.0.borrow().iter().map(|(_, v)| v).sum()
162    }
163
164    pub fn add_from(&mut self, other: &DamageResult) {
165        for type_ in other.get_types() {
166            self.add(type_, other.get(type_));
167        }
168    }
169}
170
171#[cfg(test)]
172mod test {
173    use super::DamageResult;
174    use crate::item::DamageType;
175
176    #[test]
177    fn damage_result() {
178        let dmg_result = DamageResult::new();
179        assert_eq!(dmg_result.get(DamageType::Acid), 0);
180        assert_eq!(dmg_result.get(DamageType::Bludgeoning), 0);
181
182        dmg_result.set(DamageType::Acid, 4);
183        assert_eq!(dmg_result.get(DamageType::Acid), 4);
184
185        dmg_result.add(DamageType::Bludgeoning, 8);
186        assert_eq!(dmg_result.get(DamageType::Bludgeoning), 8);
187
188        dmg_result.add(DamageType::Cold, 3);
189        assert_eq!(dmg_result.get(DamageType::Cold), 3);
190
191        dmg_result.sub(DamageType::Cold, 1);
192        assert_eq!(dmg_result.get(DamageType::Cold), 2);
193
194        assert_eq!(dmg_result.total_dmg(), 14);
195
196        assert_eq!(dmg_result.get_types().len(), 3);
197        assert_eq!(dmg_result.get_types().contains(&DamageType::Acid), true);
198        assert_eq!(
199            dmg_result.get_types().contains(&DamageType::Bludgeoning),
200            true
201        );
202        assert_eq!(dmg_result.get_types().contains(&DamageType::Cold), true);
203        assert_eq!(
204            dmg_result.get_types().contains(&DamageType::Slashing),
205            false
206        );
207    }
208}