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}