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, Default)]
60pub enum Friction {
61 #[default]
62 Normal,
63 Ski,
64 Skate,
65 }
68
69impl Friction {
70 pub fn can_skate_on(&self, b: BlockKind) -> bool {
71 match self {
72 Friction::Ski => matches!(
73 b,
74 BlockKind::Snow | BlockKind::ArtSnow | BlockKind::Ice | BlockKind::Air
75 ),
76 Friction::Skate => b == BlockKind::Ice,
77 _ => false,
78 }
79 }
80
81 pub fn get_friction(&self, b: BlockKind) -> (f32, f32) {
83 match (self, b) {
84 (Friction::Ski, BlockKind::Snow) => (0.01, 0.95),
85 (Friction::Ski, BlockKind::ArtSnow) => (0.01, 0.95),
86 (Friction::Ski, BlockKind::Ice) => (0.001, 0.5),
87 (Friction::Ski, BlockKind::Water) => (0.1, 0.7),
88 (Friction::Ski, BlockKind::Air) => (0.0, 0.0),
89 (Friction::Skate, BlockKind::Ice) => (0.001, 0.99),
90 _ => {
91 let non_directional_friction = Block::new(b, Rgb::new(0, 0, 0)).get_friction();
92 (non_directional_friction, non_directional_friction)
93 },
94 }
95 }
96}
97
98#[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize, Default)]
99#[serde(deny_unknown_fields)]
100pub struct Stats {
101 pub protection: Option<Protection>,
104 pub poise_resilience: Option<Protection>,
107 pub energy_max: Option<f32>,
109 pub energy_reward: Option<f32>,
113 pub precision_power: Option<f32>,
116 pub stealth: Option<f32>,
119 #[serde(default)]
121 pub ground_contact: Friction,
122}
123
124impl Stats {
125 fn none() -> Self {
126 Stats {
127 protection: None,
128 poise_resilience: None,
129 energy_max: None,
130 energy_reward: None,
131 precision_power: None,
132 stealth: None,
133 ground_contact: Friction::Normal,
134 }
135 }
136}
137
138#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
139pub enum StatsSource {
140 Direct(Stats),
141 FromSet(String),
142}
143
144impl Mul<f32> for Stats {
145 type Output = Self;
146
147 fn mul(self, val: f32) -> Self::Output {
148 Stats {
149 protection: self.protection.map(|a| a * val),
150 poise_resilience: self.poise_resilience.map(|a| a * val),
151 energy_max: self.energy_max.map(|a| a * val),
152 energy_reward: self.energy_reward.map(|a| a * val),
153 precision_power: self.precision_power.map(|a| a * val),
154 stealth: self.stealth.map(|a| a * val),
155 ground_contact: self.ground_contact,
157 }
158 }
159}
160
161impl Sub<Stats> for Stats {
162 type Output = Self;
163
164 fn sub(self, other: Self) -> Self::Output {
165 Self {
166 protection: self.protection.zip(other.protection).map(|(a, b)| a - b),
167 poise_resilience: self
168 .poise_resilience
169 .zip(other.poise_resilience)
170 .map(|(a, b)| a - b),
171 energy_max: self.energy_max.zip(other.energy_max).map(|(a, b)| a - b),
172 energy_reward: self
173 .energy_reward
174 .zip(other.energy_reward)
175 .map(|(a, b)| a - b),
176 precision_power: self
177 .precision_power
178 .zip(other.precision_power)
179 .map(|(a, b)| a - b),
180 stealth: self.stealth.zip(other.stealth).map(|(a, b)| a - b),
181 ground_contact: Friction::Normal,
182 }
183 }
184}
185
186#[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize)]
187pub enum Protection {
188 Invincible,
189 Normal(f32),
190}
191
192impl Default for Protection {
193 fn default() -> Self { Self::Normal(0.0) }
194}
195
196impl Sub for Protection {
197 type Output = Self;
198
199 fn sub(self, other: Self) -> Self::Output {
200 let diff = match (self, other) {
201 (Protection::Invincible, Protection::Normal(_)) => f32::INFINITY,
202 (Protection::Invincible, Protection::Invincible) => 0_f32,
203 (Protection::Normal(_), Protection::Invincible) => -f32::INFINITY,
204 (Protection::Normal(a), Protection::Normal(b)) => a - b,
205 };
206 Protection::Normal(diff)
207 }
208}
209
210impl Mul<f32> for Protection {
211 type Output = Self;
212
213 fn mul(self, val: f32) -> Self::Output {
214 match self {
215 Protection::Invincible => Protection::Invincible,
216 Protection::Normal(a) => Protection::Normal(a * val),
217 }
218 }
219}
220
221impl PartialOrd for Protection {
222 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
223 match (*self, *other) {
224 (Protection::Invincible, Protection::Invincible) => Some(Ordering::Equal),
225 (Protection::Invincible, _) => Some(Ordering::Greater),
226 (_, Protection::Invincible) => Some(Ordering::Less),
227 (Protection::Normal(a), Protection::Normal(b)) => f32::partial_cmp(&a, &b),
228 }
229 }
230}
231
232#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
233#[serde(deny_unknown_fields)]
234pub struct Armor {
235 pub kind: ArmorKind,
236 pub stats: StatsSource,
237}
238
239impl Armor {
240 pub fn new(kind: ArmorKind, stats: StatsSource) -> Self { Self { kind, stats } }
241
242 pub fn stats(
243 &self,
244 msm: &MaterialStatManifest,
245 durability_multiplier: DurabilityMultiplier,
246 ) -> Stats {
247 let base_stats = match &self.stats {
248 StatsSource::Direct(stats) => *stats,
249 StatsSource::FromSet(set) => {
250 let set_stats = msm.armor_stats(set).unwrap_or_else(Stats::none);
251 let armor_kind_weight = |kind| match kind {
252 ArmorKind::Shoulder => 2.0,
253 ArmorKind::Chest => 3.0,
254 ArmorKind::Belt => 0.5,
255 ArmorKind::Hand => 1.0,
256 ArmorKind::Pants => 2.0,
257 ArmorKind::Foot => 1.0,
258 ArmorKind::Back => 0.5,
259 ArmorKind::Backpack => 0.0,
260 ArmorKind::Ring => 0.0,
261 ArmorKind::Neck => 0.0,
262 ArmorKind::Head => 0.0,
263 ArmorKind::Tabard => 0.0,
264 ArmorKind::Bag => 0.0,
265 };
266
267 let armor_weights_sum: f32 = ArmorKind::iter().map(armor_kind_weight).sum();
268 let multiplier = armor_kind_weight(self.kind) / armor_weights_sum;
269
270 set_stats * multiplier
271 },
272 };
273 base_stats * durability_multiplier.0
274 }
275
276 #[cfg(test)]
277 pub fn test_armor(
278 kind: ArmorKind,
279 protection: Protection,
280 poise_resilience: Protection,
281 ) -> Armor {
282 Armor {
283 kind,
284 stats: StatsSource::Direct(Stats {
285 protection: Some(protection),
286 poise_resilience: Some(poise_resilience),
287 energy_max: None,
288 energy_reward: None,
289 precision_power: None,
290 stealth: None,
291 ground_contact: Friction::Normal,
292 }),
293 }
294 }
295}