veloren_common/comp/inventory/item/
armor.rs

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    /// Determines whether two pieces of armour are superficially equivalent to
51    /// one another (i.e: one may be substituted for the other in crafting
52    /// recipes or item possession checks).
53    pub fn superficially_eq(&self, other: &Self) -> bool {
54        std::mem::discriminant(&self.kind) == std::mem::discriminant(&other.kind)
55    }
56}
57
58/// longitudinal and lateral friction, only meaningful for footwear
59#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
60pub enum Friction {
61    Normal,
62    Ski,
63    Skate,
64    // Snowshoe,
65    // Spikes,
66}
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    /// longitudinal (forward) and lateral (side) friction
85    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    /// Protection is non-linearly transformed (following summation) to a damage
105    /// reduction using (prot / (60 + prot))
106    pub protection: Option<Protection>,
107    /// Poise protection is non-linearly transformed (following summation) to a
108    /// poise damage reduction using (prot / (60 + prot))
109    pub poise_resilience: Option<Protection>,
110    /// Energy max is summed, and then applied directly to the max energy stat
111    pub energy_max: Option<f32>,
112    /// Energy recovery is summed, and then added to 1.0. When attacks reward
113    /// energy, it is then multiplied by this value before the energy is
114    /// rewarded.
115    pub energy_reward: Option<f32>,
116    /// Precision power is summed, and then added to the default precision
117    /// multiplier of 1.1.
118    pub precision_power: Option<f32>,
119    /// Stealth is summed along with the base stealth bonus (2.0), and then
120    /// the agent's perception distance is divided by this value
121    pub stealth: Option<f32>,
122    /// Ground contact type, mostly for shoes
123    #[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            // There is nothing to multiply, it is just an enum
159            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}