game_features/
stat.rs

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
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
use std::collections::HashMap;
use std::fmt::Debug;
use std::hash::Hash;
// Different properties of a player/item/entity

/// The definition of a stat.
/// A stat is a named float value optionally constrained between two other values and with a
/// default value. It is used to create effects, conditions and in general to hold state
/// for each entity.
/// For example, it can be used to contain the health or mana of an entity just as well as it
/// can be used to keep track of the number of enemies positioned around an entity.
#[derive(Debug, Clone, Serialize, Deserialize, new, Builder)]
pub struct StatDefinition<K> {
    /// The key.
    pub key: K,
    /// The name.
    pub name: String,
    /// The computer friendly name.
    pub friendly_name: String,
    /// The default value.
    pub default_value: f64,
    /// The minimum value.
    #[new(default)]
    pub min_value: Option<f64>,
    /// The maximum value.
    #[new(default)]
    pub max_value: Option<f64>,
    /// The icon of this stat.
    #[new(default)]
    pub icon_path: Option<String>,
}

impl<K: Clone> StatDefinition<K> {
    /// Creates the default StatInstance for this StatDefinition.
    pub fn default_instance(&self) -> StatInstance<K> {
        StatInstance::new(self.key.clone(), self.default_value)
    }
}

/// An instance of a stat.
/// Contains a base value as well as a value after applying the stat effectors.
#[derive(Debug, Clone, Serialize, Deserialize, new, Builder)]
pub struct StatInstance<K> {
    /// The key of the stat.
    pub key: K,
    /// The base value of the stat.
    pub value: f64,
    /// The value of this stat after applying the effectors.
    #[new(value = "value")]
    pub value_with_effectors: f64,
}

/// The definitions of all known stats.
#[derive(Debug, Clone, Serialize, Deserialize, new)]
pub struct StatDefinitions<K: Hash + Eq> {
    /// The definitions.
    pub defs: HashMap<K, StatDefinition<K>>,
}

impl<K: Hash + Eq> Default for StatDefinitions<K> {
    fn default() -> Self {
        Self {
            defs: HashMap::default(),
        }
    }
}

impl<K: Hash + Eq + Clone> StatDefinitions<K> {
    /// Converts the `StatDefinitions` into a `StatSet` using the default stat values.
    pub fn to_statset(&self) -> StatSet<K> {
        let instances = self
            .defs
            .iter()
            .map(|(k, v)| (k.clone(), v.default_instance()))
            .collect::<HashMap<_, _>>();
        StatSet::new(instances)
    }
}

impl<K: Hash + Eq + Clone> From<Vec<StatDefinition<K>>> for StatDefinitions<K> {
    fn from(t: Vec<StatDefinition<K>>) -> Self {
        let defs = t
            .into_iter()
            .map(|s| (s.key.clone(), s))
            .collect::<HashMap<_, _>>();
        Self::new(defs)
    }
}

/// Holds the instances of all the stats an entity has.
#[derive(Debug, Clone, Default, Serialize, Deserialize, new)]
pub struct StatSet<K: Hash + Eq> {
    /// The stats.
    pub stats: HashMap<K, StatInstance<K>>,
}

//impl<K: Hash+Eq> StatSet<K> {
//    pub fn update(&mut self, delta_time: f64, stat_set: &mut StatSet<K>) {
//        let mut rm_idx = vec![];
//        for (idx, stat) in self.effectors.iter_mut().enumerate() {
//            // TODO: apply modifier rules and ordering.
//
//            if let Some(left) = stat.disable_in.as_mut() {
//                *left -= delta_time;
//                if *left <= 0.0 {
//                    rm_idx.push(idx);
//                }
//            }
//        }
//
//        rm_idx.reverse();
//        for idx in rm_idx {
//            self.effectors.swap_remove(idx);
//        }
//    }
//}

/// Condition based on a stat to activate something.
#[derive(Clone, Debug, Serialize, Deserialize, new)]
pub struct StatCondition<K> {
    /// The key of the stat.
    pub stat_key: K,
    /// The type of condition.
    pub condition: StatConditionType,
}

impl<K: Hash + Eq + Debug> StatCondition<K> {
    /// Checks if this stat condition is met using for the provided `StatSet` using the known
    /// `StatDefinitions`.
    pub fn check(&self, stats: &StatSet<K>, stat_defs: &StatDefinitions<K>) -> bool {
        let v = stats.stats.get(&self.stat_key).expect(&format!(
            "Requested stat key {:?} is not in provided StatSet.",
            self.stat_key
        ));
        let def = stat_defs.defs.get(&self.stat_key).expect(&format!(
            "Requested stat key {:?} is not in provided StatDefinitions.",
            self.stat_key
        ));
        self.condition
            .is_true(v.value, def.min_value, def.max_value)
    }
}

/// A condition based on a stat's value.
#[derive(Clone, Serialize, Deserialize, new, Debug)]
pub enum StatConditionType {
    /// The stat value must be higher or equal to this value.
    MinValue(f64),
    /// The stat value must be between these values.
    BetweenValue(f64, f64),
    /// The stat value must be lower or equal to this value.
    MaxValue(f64),
    /// The minimum progress of the value between its minimum and maximum.
    /// This calculates the distance between the minimum and maximum values, then assigns
    /// a value between 0.0 and 1.0 that correspond to the absolute distance from the minimum.
    /// If the minimum value is 10 and the maximum is 20 and we have a value of 15, then this
    /// corresponds to a "distance" of 0.5 (50%!) of the way between 10 and 20.
    MinPercent(f64),
    /// The minimum progress of the value between its minimum and maximum.
    /// This calculates the distance between the minimum and maximum values, then assigns
    /// a value between 0.0 and 1.0 that correspond to the absolute distance from the minimum.
    /// If the minimum value is 10 and the maximum is 20 and we have a value of 15, then this
    /// corresponds to a "distance" of 0.5 (50%!) of the way between 10 and 20.
    BetweenPercent(f64, f64),
    /// The minimum progress of the value between its minimum and maximum.
    /// This calculates the distance between the minimum and maximum values, then assigns
    /// a value between 0.0 and 1.0 that correspond to the absolute distance from the minimum.
    /// If the minimum value is 10 and the maximum is 20 and we have a value of 15, then this
    /// corresponds to a "distance" of 0.5 (50%!) of the way between 10 and 20.
    MaxPercent(f64),
    /// The value is divisible by this value.
    /// DivisibleBy(2) is equivalent to (value % 2 == 0).
    DivisibleBy(i32),
}

impl StatConditionType {
    /// Checks if the condition is true using the actual value, as well as the minimum and maximum
    /// values of the stat (found in the `StatDefinition`).
    pub fn is_true(&self, value: f64, min_value: Option<f64>, max_value: Option<f64>) -> bool {
        let percent = if let (Some(min_value), Some(max_value)) = (min_value, max_value) {
            Some((value - min_value) / (max_value - min_value))
        } else {
            None
        };
        match &*self {
            StatConditionType::MinValue(v) => value >= *v,
            StatConditionType::BetweenValue(min, max) => value >= *min && value <= *max,
            StatConditionType::MaxValue(v) => value <= *v,
            StatConditionType::MinPercent(p) => {
                percent.expect("This stat doesn't have min/max values.") >= *p
            }
            StatConditionType::BetweenPercent(min, max) => {
                percent.expect("This stat doesn't have min/max values.") >= *min
                    && percent.expect("This stat doesn't have min/max values.") <= *max
            }
            StatConditionType::MaxPercent(p) => {
                percent.expect("This stat doesn't have min/max values.") <= *p
            }
            StatConditionType::DivisibleBy(p) => value as i32 % p == 0,
        }
    }
}