1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
use serde::{Deserialize, Serialize};
use specs::{Component, DerefFlaggedStorage};
use std::{error::Error, fmt};

use crate::combat::{AttackEffect, DamagedEffect, DeathEffect};

use super::Body;

#[derive(Debug)]
#[allow(dead_code)] // TODO: remove once trade sim hits master
pub enum StatChangeError {
    Underflow,
    Overflow,
}

#[derive(Debug, Clone, Copy, Deserialize, Serialize)]
pub struct StatsModifier {
    pub add_mod: f32,
    pub mult_mod: f32,
}

impl Default for StatsModifier {
    fn default() -> Self {
        Self {
            add_mod: 0.0,
            mult_mod: 1.0,
        }
    }
}

#[derive(Debug, Clone, Copy, Deserialize, Serialize)]
pub struct StatsSplit {
    pub pos_mod: f32,
    pub neg_mod: f32,
}

impl Default for StatsSplit {
    fn default() -> Self {
        Self {
            pos_mod: 0.0,
            neg_mod: 0.0,
        }
    }
}

impl StatsSplit {
    pub fn modifier(&self) -> f32 { self.pos_mod + self.neg_mod }
}

impl StatsModifier {
    pub fn compute_maximum(&self, base_value: f32) -> f32 {
        base_value * self.mult_mod + self.add_mod
    }

    // Note: unused for now
    pub fn update_maximum(&self) -> bool {
        self.add_mod.abs() > f32::EPSILON || (self.mult_mod - 1.0).abs() > f32::EPSILON
    }
}

impl fmt::Display for StatChangeError {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "{}", match self {
            Self::Underflow => "insufficient stat quantity",
            Self::Overflow => "stat quantity would overflow",
        })
    }
}
impl Error for StatChangeError {}

#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct Stats {
    pub name: String,
    pub original_body: Body,
    pub damage_reduction: StatsSplit,
    pub poise_reduction: StatsSplit,
    pub max_health_modifiers: StatsModifier,
    pub move_speed_modifier: f32,
    pub jump_modifier: f32,
    pub attack_speed_modifier: f32,
    pub recovery_speed_modifier: f32,
    pub friction_modifier: f32,
    pub max_energy_modifiers: StatsModifier,
    pub poise_damage_modifier: f32,
    pub attack_damage_modifier: f32,
    pub precision_multiplier_override: Option<f32>,
    pub precision_vulnerability_multiplier_override: Option<f32>,
    pub swim_speed_modifier: f32,
    /// This adds effects to any attacks that the entity makes
    pub effects_on_attack: Vec<AttackEffect>,
    /// This is the fraction of damage reduction (from armor and other buffs)
    /// that gets ignored by attacks from this entity
    pub mitigations_penetration: f32,
    pub energy_reward_modifier: f32,
    /// This creates effects when the entity is damaged
    pub effects_on_damaged: Vec<DamagedEffect>,
    /// This creates effects when the entity is killed
    pub effects_on_death: Vec<DeathEffect>,
    pub disable_auxiliary_abilities: bool,
    pub crowd_control_resistance: f32,
    pub item_effect_reduction: f32,
}

impl Stats {
    pub fn new(name: String, body: Body) -> Self {
        Self {
            name,
            original_body: body,
            damage_reduction: StatsSplit::default(),
            poise_reduction: StatsSplit::default(),
            max_health_modifiers: StatsModifier::default(),
            move_speed_modifier: 1.0,
            jump_modifier: 1.0,
            attack_speed_modifier: 1.0,
            recovery_speed_modifier: 1.0,
            friction_modifier: 1.0,
            max_energy_modifiers: StatsModifier::default(),
            poise_damage_modifier: 1.0,
            attack_damage_modifier: 1.0,
            precision_multiplier_override: None,
            precision_vulnerability_multiplier_override: None,
            swim_speed_modifier: 1.0,
            effects_on_attack: Vec::new(),
            mitigations_penetration: 0.0,
            energy_reward_modifier: 1.0,
            effects_on_damaged: Vec::new(),
            effects_on_death: Vec::new(),
            disable_auxiliary_abilities: false,
            crowd_control_resistance: 0.0,
            item_effect_reduction: 1.0,
        }
    }

    /// Creates an empty `Stats` instance - used during character loading from
    /// the database
    pub fn empty(body: Body) -> Self { Self::new("".to_string(), body) }

    /// Resets temporary modifiers to default values
    pub fn reset_temp_modifiers(&mut self) {
        let name = std::mem::take(&mut self.name);
        let body = self.original_body;

        *self = Self::new(name, body);
    }
}

impl Component for Stats {
    type Storage = DerefFlaggedStorage<Self, specs::VecStorage<Self>>;
}