1use crate::{
2 comp::item::{DurabilityMultiplier, MaterialStatManifest, Rgb},
3 terrain::{Block, BlockKind},
4};
5use serde::{Deserialize, Serialize};
6use std::{
7 cmp::Ordering,
8 ops::{Mul, Sub},
9};
10use strum::{EnumIter, IntoEnumIterator};
11
12#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize, EnumIter)]
13pub enum ArmorKind {
14 Shoulder,
15 Chest,
16 Belt,
17 Hand,
18 Pants,
19 Foot,
20 Back,
21 Ring,
22 Neck,
23 Head,
24 Tabard,
25 Bag,
26 Backpack,
27}
28
29impl ArmorKind {
30 pub fn has_durability(self) -> bool {
31 match self {
32 ArmorKind::Shoulder => true,
33 ArmorKind::Chest => true,
34 ArmorKind::Belt => true,
35 ArmorKind::Hand => true,
36 ArmorKind::Pants => true,
37 ArmorKind::Foot => true,
38 ArmorKind::Back => true,
39 ArmorKind::Backpack => true,
40 ArmorKind::Ring => false,
41 ArmorKind::Neck => false,
42 ArmorKind::Head => true,
43 ArmorKind::Tabard => false,
44 ArmorKind::Bag => false,
45 }
46 }
47}
48
49impl Armor {
50 pub fn superficially_eq(&self, other: &Self) -> bool {
54 std::mem::discriminant(&self.kind) == std::mem::discriminant(&other.kind)
55 }
56}
57
58#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
60pub enum Friction {
61 Normal,
62 Ski,
63 Skate,
64 }
67
68impl Default for Friction {
69 fn default() -> Self { Self::Normal }
70}
71
72impl Friction {
73 pub fn can_skate_on(&self, b: BlockKind) -> bool {
74 match self {
75 Friction::Ski => matches!(
76 b,
77 BlockKind::Snow | BlockKind::ArtSnow | BlockKind::Ice | BlockKind::Air
78 ),
79 Friction::Skate => b == BlockKind::Ice,
80 _ => false,
81 }
82 }
83
84 pub fn get_friction(&self, b: BlockKind) -> (f32, f32) {
86 match (self, b) {
87 (Friction::Ski, BlockKind::Snow) => (0.01, 0.95),
88 (Friction::Ski, BlockKind::ArtSnow) => (0.01, 0.95),
89 (Friction::Ski, BlockKind::Ice) => (0.001, 0.5),
90 (Friction::Ski, BlockKind::Water) => (0.1, 0.7),
91 (Friction::Ski, BlockKind::Air) => (0.0, 0.0),
92 (Friction::Skate, BlockKind::Ice) => (0.001, 0.99),
93 _ => {
94 let non_directional_friction = Block::new(b, Rgb::new(0, 0, 0)).get_friction();
95 (non_directional_friction, non_directional_friction)
96 },
97 }
98 }
99}
100
101#[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize, Default)]
102#[serde(deny_unknown_fields)]
103pub struct Stats {
104 pub protection: Option<Protection>,
107 pub poise_resilience: Option<Protection>,
110 pub energy_max: Option<f32>,
112 pub energy_reward: Option<f32>,
116 pub precision_power: Option<f32>,
119 pub stealth: Option<f32>,
122 #[serde(default)]
124 pub ground_contact: Friction,
125}
126
127impl Stats {
128 fn none() -> Self {
129 Stats {
130 protection: None,
131 poise_resilience: None,
132 energy_max: None,
133 energy_reward: None,
134 precision_power: None,
135 stealth: None,
136 ground_contact: Friction::Normal,
137 }
138 }
139}
140
141#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
142pub enum StatsSource {
143 Direct(Stats),
144 FromSet(String),
145}
146
147impl Mul<f32> for Stats {
148 type Output = Self;
149
150 fn mul(self, val: f32) -> Self::Output {
151 Stats {
152 protection: self.protection.map(|a| a * val),
153 poise_resilience: self.poise_resilience.map(|a| a * val),
154 energy_max: self.energy_max.map(|a| a * val),
155 energy_reward: self.energy_reward.map(|a| a * val),
156 precision_power: self.precision_power.map(|a| a * val),
157 stealth: self.stealth.map(|a| a * val),
158 ground_contact: self.ground_contact,
160 }
161 }
162}
163
164impl Sub<Stats> for Stats {
165 type Output = Self;
166
167 fn sub(self, other: Self) -> Self::Output {
168 Self {
169 protection: self.protection.zip(other.protection).map(|(a, b)| a - b),
170 poise_resilience: self
171 .poise_resilience
172 .zip(other.poise_resilience)
173 .map(|(a, b)| a - b),
174 energy_max: self.energy_max.zip(other.energy_max).map(|(a, b)| a - b),
175 energy_reward: self
176 .energy_reward
177 .zip(other.energy_reward)
178 .map(|(a, b)| a - b),
179 precision_power: self
180 .precision_power
181 .zip(other.precision_power)
182 .map(|(a, b)| a - b),
183 stealth: self.stealth.zip(other.stealth).map(|(a, b)| a - b),
184 ground_contact: Friction::Normal,
185 }
186 }
187}
188
189#[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize)]
190pub enum Protection {
191 Invincible,
192 Normal(f32),
193}
194
195impl Default for Protection {
196 fn default() -> Self { Self::Normal(0.0) }
197}
198
199impl Sub for Protection {
200 type Output = Self;
201
202 fn sub(self, other: Self) -> Self::Output {
203 let diff = match (self, other) {
204 (Protection::Invincible, Protection::Normal(_)) => f32::INFINITY,
205 (Protection::Invincible, Protection::Invincible) => 0_f32,
206 (Protection::Normal(_), Protection::Invincible) => -f32::INFINITY,
207 (Protection::Normal(a), Protection::Normal(b)) => a - b,
208 };
209 Protection::Normal(diff)
210 }
211}
212
213impl Mul<f32> for Protection {
214 type Output = Self;
215
216 fn mul(self, val: f32) -> Self::Output {
217 match self {
218 Protection::Invincible => Protection::Invincible,
219 Protection::Normal(a) => Protection::Normal(a * val),
220 }
221 }
222}
223
224impl PartialOrd for Protection {
225 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
226 match (*self, *other) {
227 (Protection::Invincible, Protection::Invincible) => Some(Ordering::Equal),
228 (Protection::Invincible, _) => Some(Ordering::Greater),
229 (_, Protection::Invincible) => Some(Ordering::Less),
230 (Protection::Normal(a), Protection::Normal(b)) => f32::partial_cmp(&a, &b),
231 }
232 }
233}
234
235#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
236#[serde(deny_unknown_fields)]
237pub struct Armor {
238 pub kind: ArmorKind,
239 pub stats: StatsSource,
240}
241
242impl Armor {
243 pub fn new(kind: ArmorKind, stats: StatsSource) -> Self { Self { kind, stats } }
244
245 pub fn stats(
246 &self,
247 msm: &MaterialStatManifest,
248 durability_multiplier: DurabilityMultiplier,
249 ) -> Stats {
250 let base_stats = match &self.stats {
251 StatsSource::Direct(stats) => *stats,
252 StatsSource::FromSet(set) => {
253 let set_stats = msm.armor_stats(set).unwrap_or_else(Stats::none);
254 let armor_kind_weight = |kind| match kind {
255 ArmorKind::Shoulder => 2.0,
256 ArmorKind::Chest => 3.0,
257 ArmorKind::Belt => 0.5,
258 ArmorKind::Hand => 1.0,
259 ArmorKind::Pants => 2.0,
260 ArmorKind::Foot => 1.0,
261 ArmorKind::Back => 0.5,
262 ArmorKind::Backpack => 0.0,
263 ArmorKind::Ring => 0.0,
264 ArmorKind::Neck => 0.0,
265 ArmorKind::Head => 0.0,
266 ArmorKind::Tabard => 0.0,
267 ArmorKind::Bag => 0.0,
268 };
269
270 let armor_weights_sum: f32 = ArmorKind::iter().map(armor_kind_weight).sum();
271 let multiplier = armor_kind_weight(self.kind) / armor_weights_sum;
272
273 set_stats * multiplier
274 },
275 };
276 base_stats * durability_multiplier.0
277 }
278
279 #[cfg(test)]
280 pub fn test_armor(
281 kind: ArmorKind,
282 protection: Protection,
283 poise_resilience: Protection,
284 ) -> Armor {
285 Armor {
286 kind,
287 stats: StatsSource::Direct(Stats {
288 protection: Some(protection),
289 poise_resilience: Some(poise_resilience),
290 energy_max: None,
291 energy_reward: None,
292 precision_power: None,
293 stealth: None,
294 ground_contact: Friction::Normal,
295 }),
296 }
297 }
298}