veloren_common/comp/
stats.rs

1use common_i18n::Content;
2use serde::{Deserialize, Serialize};
3use specs::{Component, DerefFlaggedStorage};
4use std::{error::Error, fmt};
5
6use crate::combat::{AttackEffect, DamagedEffect, DeathEffect};
7
8use super::Body;
9
10#[derive(Debug)]
11#[expect(dead_code)] // TODO: remove once trade sim hits master
12pub enum StatChangeError {
13    Underflow,
14    Overflow,
15}
16
17#[derive(Debug, Clone, Copy, Deserialize, Serialize)]
18pub struct StatsModifier {
19    pub add_mod: f32,
20    pub mult_mod: f32,
21}
22
23impl Default for StatsModifier {
24    fn default() -> Self {
25        Self {
26            add_mod: 0.0,
27            mult_mod: 1.0,
28        }
29    }
30}
31
32#[derive(Debug, Clone, Copy, Deserialize, Serialize)]
33pub struct StatsSplit {
34    pub pos_mod: f32,
35    pub neg_mod: f32,
36}
37
38impl Default for StatsSplit {
39    fn default() -> Self {
40        Self {
41            pos_mod: 0.0,
42            neg_mod: 0.0,
43        }
44    }
45}
46
47impl StatsSplit {
48    pub fn modifier(&self) -> f32 { self.pos_mod + self.neg_mod }
49}
50
51impl StatsModifier {
52    pub fn compute_maximum(&self, base_value: f32) -> f32 {
53        base_value * self.mult_mod + self.add_mod
54    }
55
56    // Note: unused for now
57    pub fn update_maximum(&self) -> bool {
58        self.add_mod.abs() > f32::EPSILON || (self.mult_mod - 1.0).abs() > f32::EPSILON
59    }
60}
61
62impl fmt::Display for StatChangeError {
63    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
64        write!(f, "{}", match self {
65            Self::Underflow => "insufficient stat quantity",
66            Self::Overflow => "stat quantity would overflow",
67        })
68    }
69}
70impl Error for StatChangeError {}
71
72#[derive(Clone, Debug, Serialize, Deserialize)]
73pub struct Stats {
74    pub name: Content,
75    pub original_body: Body,
76    pub damage_reduction: StatsSplit,
77    pub poise_reduction: StatsSplit,
78    pub max_health_modifiers: StatsModifier,
79    pub move_speed_modifier: f32,
80    pub jump_modifier: f32,
81    pub attack_speed_modifier: f32,
82    pub recovery_speed_modifier: f32,
83    pub friction_modifier: f32,
84    pub max_energy_modifiers: StatsModifier,
85    pub poise_damage_modifier: f32,
86    pub attack_damage_modifier: f32,
87    pub precision_multiplier_override: Option<f32>,
88    pub precision_vulnerability_multiplier_override: Option<f32>,
89    pub swim_speed_modifier: f32,
90    /// This adds effects to any attacks that the entity makes
91    pub effects_on_attack: Vec<AttackEffect>,
92    /// This is the fraction of damage reduction (from armor and other buffs)
93    /// that gets ignored by attacks from this entity
94    pub mitigations_penetration: f32,
95    pub energy_reward_modifier: f32,
96    /// This creates effects when the entity is damaged
97    pub effects_on_damaged: Vec<DamagedEffect>,
98    /// This creates effects when the entity is killed
99    pub effects_on_death: Vec<DeathEffect>,
100    pub disable_auxiliary_abilities: bool,
101    pub crowd_control_resistance: f32,
102    pub item_effect_reduction: f32,
103}
104
105impl Stats {
106    pub fn new(name: Content, body: Body) -> Self {
107        Self {
108            name,
109            original_body: body,
110            damage_reduction: StatsSplit::default(),
111            poise_reduction: StatsSplit::default(),
112            max_health_modifiers: StatsModifier::default(),
113            move_speed_modifier: 1.0,
114            jump_modifier: 1.0,
115            attack_speed_modifier: 1.0,
116            recovery_speed_modifier: 1.0,
117            friction_modifier: 1.0,
118            max_energy_modifiers: StatsModifier::default(),
119            poise_damage_modifier: 1.0,
120            attack_damage_modifier: 1.0,
121            precision_multiplier_override: None,
122            precision_vulnerability_multiplier_override: None,
123            swim_speed_modifier: 1.0,
124            effects_on_attack: Vec::new(),
125            mitigations_penetration: 0.0,
126            energy_reward_modifier: 1.0,
127            effects_on_damaged: Vec::new(),
128            effects_on_death: Vec::new(),
129            disable_auxiliary_abilities: false,
130            crowd_control_resistance: 0.0,
131            item_effect_reduction: 1.0,
132        }
133    }
134
135    /// Creates an empty `Stats` instance - used during character loading from
136    /// the database
137    pub fn empty(body: Body) -> Self { Self::new(Content::dummy(), body) }
138
139    /// Resets temporary modifiers to default values
140    pub fn reset_temp_modifiers(&mut self) {
141        // "consume" name and body and re-create from scratch
142        let name = std::mem::replace(&mut self.name, Content::dummy());
143        let body = self.original_body;
144
145        *self = Self::new(name, body);
146    }
147}
148
149impl Component for Stats {
150    type Storage = DerefFlaggedStorage<Self, specs::VecStorage<Self>>;
151}