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, Default)]
60pub enum Friction {
61    #[default]
62    Normal,
63    Ski,
64    Skate,
65    // Snowshoe,
66    // Spikes,
67}
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    /// longitudinal (forward) and lateral (side) friction
82    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    /// Protection is non-linearly transformed (following summation) to a damage
102    /// reduction using (prot / (60 + prot))
103    pub protection: Option<Protection>,
104    /// Poise protection is non-linearly transformed (following summation) to a
105    /// poise damage reduction using (prot / (60 + prot))
106    pub poise_resilience: Option<Protection>,
107    /// Energy max is summed, and then applied directly to the max energy stat
108    pub energy_max: Option<f32>,
109    /// Energy recovery is summed, and then added to 1.0. When attacks reward
110    /// energy, it is then multiplied by this value before the energy is
111    /// rewarded.
112    pub energy_reward: Option<f32>,
113    /// Precision power is summed, and then added to the default precision
114    /// multiplier of 1.1.
115    pub precision_power: Option<f32>,
116    /// Stealth is summed along with the base stealth bonus (2.0), and then
117    /// the agent's perception distance is divided by this value
118    pub stealth: Option<f32>,
119    /// Ground contact type, mostly for shoes
120    #[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            // There is nothing to multiply, it is just an enum
156            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}