Skip to main content

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::{
7    combat::{AttackEffect, AttackedModification, CombatRequirement, StatEffect},
8    comp::projectile::ProjectileConstructorEffect,
9    uid::Uid,
10};
11
12use super::Body;
13
14#[derive(Debug)]
15#[expect(dead_code)] // TODO: remove once trade sim hits master
16pub enum StatChangeError {
17    Underflow,
18    Overflow,
19}
20
21#[derive(Debug, Clone, Copy, Deserialize, Serialize)]
22pub struct StatsModifier {
23    pub add_mod: f32,
24    pub mult_mod: f32,
25}
26
27impl Default for StatsModifier {
28    fn default() -> Self {
29        Self {
30            add_mod: 0.0,
31            mult_mod: 1.0,
32        }
33    }
34}
35
36#[derive(Debug, Clone, Copy, Deserialize, Serialize)]
37pub struct StatsSplit {
38    pub pos_mod: f32,
39    pub neg_mod: f32,
40}
41
42impl Default for StatsSplit {
43    fn default() -> Self {
44        Self {
45            pos_mod: 0.0,
46            neg_mod: 0.0,
47        }
48    }
49}
50
51impl StatsSplit {
52    pub fn modifier(&self) -> f32 { self.pos_mod + self.neg_mod }
53}
54
55impl StatsModifier {
56    pub fn compute_maximum(&self, base_value: f32) -> f32 {
57        base_value * self.mult_mod + self.add_mod
58    }
59
60    // Note: unused for now
61    pub fn update_maximum(&self) -> bool {
62        self.add_mod.abs() > f32::EPSILON || (self.mult_mod - 1.0).abs() > f32::EPSILON
63    }
64}
65
66impl fmt::Display for StatChangeError {
67    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
68        write!(f, "{}", match self {
69            Self::Underflow => "insufficient stat quantity",
70            Self::Overflow => "stat quantity would overflow",
71        })
72    }
73}
74impl Error for StatChangeError {}
75
76#[derive(Clone, Debug, Serialize, Deserialize)]
77pub struct Stats {
78    pub name: Content,
79    pub original_body: Body,
80    pub damage_reduction: StatsSplit,
81    pub poise_reduction: StatsSplit,
82    pub max_health_modifiers: StatsModifier,
83    pub move_speed_modifier: f32,
84    pub charge_move_speed_modifier: f32,
85    pub buildup_move_speed_modifier: f32,
86    pub jump_modifier: f32,
87    pub attack_speed_modifier: f32,
88    pub charge_speed_modifier: f32,
89    pub buildup_speed_modifier: f32,
90    pub recovery_speed_modifier: f32,
91    pub friction_modifier: f32,
92    pub max_energy_modifiers: StatsModifier,
93    pub poise_damage_modifier: f32,
94    pub attack_damage_modifier: f32,
95    pub conditional_precision_modifiers: Vec<(Option<CombatRequirement>, f32, bool)>,
96    pub precision_vulnerability_multiplier_override: Option<f32>,
97    pub swim_speed_modifier: f32,
98    /// This adds effects to any attacks that the entity makes
99    pub effects_on_attack: Vec<AttackEffect>,
100    /// This is the fraction of damage reduction (from armor and other buffs)
101    /// that gets ignored by attacks from this entity
102    pub mitigations_penetration: f32,
103    pub energy_reward_modifier: f32,
104    pub energy_efficiency_modifier: f32,
105    /// This creates effects when the entity is damaged
106    pub effects_on_damaged: Vec<StatEffect>,
107    /// This creates effects when the entity is killed
108    pub effects_on_death: Vec<StatEffect>,
109    pub disable_auxiliary_abilities: bool,
110    pub crowd_control_resistance: f32,
111    pub item_effect_reduction: f32,
112    /// This modifies attacks that target this entity
113    pub attacked_modifications: Vec<AttackedModification>,
114    pub precision_power_mult: f32,
115    pub knockback_mult: f32,
116    pub projectile_speed_mult: f32,
117    pub projectile_constructor_effects: Vec<ProjectileConstructorEffect>,
118    /// This technically doesn't do anything. It should be used in the frontend
119    /// to 'mark' an entity for a player, or used in agent to make an NPC focus
120    /// on an entity.
121    pub marked_entities: Vec<Uid>,
122}
123
124impl Stats {
125    pub fn new(name: Content, body: Body) -> Self {
126        Self {
127            name,
128            original_body: body,
129            damage_reduction: StatsSplit::default(),
130            poise_reduction: StatsSplit::default(),
131            max_health_modifiers: StatsModifier::default(),
132            move_speed_modifier: 1.0,
133            charge_move_speed_modifier: 1.0,
134            buildup_move_speed_modifier: 1.0,
135            jump_modifier: 1.0,
136            attack_speed_modifier: 1.0,
137            recovery_speed_modifier: 1.0,
138            charge_speed_modifier: 1.0,
139            buildup_speed_modifier: 1.0,
140            friction_modifier: 1.0,
141            max_energy_modifiers: StatsModifier::default(),
142            poise_damage_modifier: 1.0,
143            attack_damage_modifier: 1.0,
144            conditional_precision_modifiers: Vec::new(),
145            precision_vulnerability_multiplier_override: None,
146            swim_speed_modifier: 1.0,
147            effects_on_attack: Vec::new(),
148            mitigations_penetration: 0.0,
149            energy_reward_modifier: 1.0,
150            energy_efficiency_modifier: 1.0,
151            effects_on_damaged: Vec::new(),
152            effects_on_death: Vec::new(),
153            disable_auxiliary_abilities: false,
154            crowd_control_resistance: 0.0,
155            item_effect_reduction: 1.0,
156            attacked_modifications: Vec::new(),
157            precision_power_mult: 1.0,
158            knockback_mult: 1.0,
159            projectile_speed_mult: 1.0,
160            projectile_constructor_effects: Vec::new(),
161            marked_entities: Vec::new(),
162        }
163    }
164
165    /// Creates an empty `Stats` instance - used during character loading from
166    /// the database
167    pub fn empty(body: Body) -> Self { Self::new(Content::dummy(), body) }
168
169    /// Resets temporary modifiers to default values
170    pub fn reset_temp_modifiers(&mut self) {
171        // "consume" name and body and re-create from scratch
172        let name = std::mem::replace(&mut self.name, Content::dummy());
173        let body = self.original_body;
174
175        *self = Self::new(name, body);
176    }
177}
178
179impl Component for Stats {
180    type Storage = DerefFlaggedStorage<Self, specs::VecStorage<Self>>;
181}