tf2_enum/traits.rs
1use crate::{AttributeDef, AttributeValue, ItemAttribute};
2use crate::error::InsertError;
3
4/// Attribute values for an item attribute.
5pub trait Attribute: Sized {
6 /// The defindex.
7 const DEFINDEX: u32;
8 /// The attribute definition.
9 const ATTRIBUTE: AttributeDef;
10 /// **Not part of the schema.**
11 ///
12 /// This is a marker to specify which attribute field is meaningful to us in obtaining the
13 /// attribute's value.
14 ///
15 /// # Kill Count Example
16 /// ```json
17 /// {
18 /// "defindex": 214,
19 /// "value": 918,
20 /// "float_value": 1.28639199025018207e-42
21 /// }
22 /// ```
23 /// This is the "kill_eater" attribute. "918" refers to the number of kills. The `float_value`
24 /// field is the same number as a 32-bit float.
25 ///
26 /// You can perform the conversions yourself with the following code:
27 /// ```
28 /// let value = 918u32;
29 /// let float_value = 1.28639199025018207e-42f32;
30 ///
31 /// assert_eq!(f32::from_bits(value), float_value);
32 /// assert_eq!(float_value.to_bits(), value);
33 /// ```
34 ///
35 /// # Sheen Example
36 /// ```json
37 /// {
38 /// "defindex": 2014,
39 /// "value": 1086324736,
40 /// "float_value": 6
41 /// }
42 /// ```
43 /// This is the "killstreak_idleeffect" attribute. "6" refers to the associated sheen (
44 /// [`Sheen::VillainousViolet`][`crate::Sheen::VillainousViolet`]), but is stored in the
45 /// `float_value` field, unlike "kill_eater". The `value` field is the same number as a 32-bit
46 /// float.
47 ///
48 /// While both values refer to the same value, and internally the attribute's value is its
49 /// `float_value`, there are many cases where the `float_value` doesn't mean anything to us
50 /// unless converted to a 32-bit integer from its bits, and if the `float_value` does mean
51 /// something to us we don't want to convert it to an integer. This can be a little confusing,
52 /// but it's just how the API is.
53 ///
54 /// By marking each attribute with a `uses_float_value` flag, we can indicate whether the
55 /// `float_value` field is meaningful to use for that attribute.
56 const USES_FLOAT_VALUE: bool;
57
58 /// Gets the attribute value.
59 fn attribute_value(&self) -> AttributeValue {
60 self.attribute_float_value()
61 .map(|v| v.to_bits().into())
62 .unwrap_or_default()
63 }
64
65 /// Gets the attribute float value.
66 fn attribute_float_value(&self) -> Option<f32>;
67}
68
69/// Associated attribute values for a set of item attributes.
70pub trait Attributes: Sized {
71 /// The list of associated defindexes.
72 const DEFINDEX: &'static [u32];
73 /// The attribute definition.
74 const ATTRIBUTES: &'static [AttributeDef];
75 /// See [`Attribute::USES_FLOAT_VALUE`]. This applies to all attributes in the set.
76 const USES_FLOAT_VALUE: bool;
77
78 /// Gets the attribute value.
79 fn attribute_value(&self) -> AttributeValue {
80 self.attribute_float_value()
81 .map(|v| v.to_bits().into())
82 .unwrap_or_default()
83 }
84
85 /// Gets the attribute float value.
86 fn attribute_float_value(&self) -> Option<f32> {
87 None
88 }
89
90 /// Gets the attribute definition for a given defindex.
91 fn get_attribute_def_by_defindex(defindex: u32) -> Option<&'static AttributeDef> {
92 Self::ATTRIBUTES.iter().find(|attr| attr.defindex == defindex)
93 }
94}
95
96/// Backwards conversion for attributes associated with an integer value.
97pub trait TryFromIntAttributeValue: Sized + TryFrom<u32> {
98 /// Attempts conversion from an attribute value.
99 #[allow(unused_variables)]
100 fn try_from_attribute_value(v: AttributeValue) -> Option<Self> {
101 None
102 }
103
104 /// Attempts conversion from an attribute float value.
105 fn try_from_attribute_float_value(v: f32) -> Option<Self> {
106 if v.fract() != 0.0 || v.is_sign_negative() || v > (u32::MAX as f32) {
107 return None;
108 }
109
110 Self::try_from(v as u32).ok()
111 }
112}
113
114/// Definitions which are associated with colors.
115pub trait Colored: Sized {
116 /// Gets the color.
117 fn color(&self) -> u32;
118
119 /// Attempts to convert a hexadecimal color.
120 fn from_color(color: u32) -> Option<Self>;
121
122 /// Converts this into a hexademical color string in the format "#FFFFFF".
123 #[inline]
124 fn color_string(&self) -> String {
125 format!("#{:06X}", self.color())
126 }
127
128 /// Attempts to convert a hexadecimal color string.
129 fn from_color_str<S: AsRef<str>>(color: S) -> Option<Self> {
130 Self::from_color(extract_color(color.as_ref())?)
131 }
132}
133
134/// Definitions which are associated with an item defindex.
135pub trait HasItemDefindex: Sized {
136 /// Gets the `defindex`.
137 fn defindex(&self) -> u32;
138
139 /// Converts a `defindex` into its related item, if it exists.
140 fn from_defindex(defindex: u32) -> Option<Self>;
141}
142
143/// A fixed set of attributes.
144pub trait AttributeSet: Sized + Default {
145 /// Max number of items.
146 const MAX_COUNT: usize;
147 /// The item type.
148 type Item: PartialEq + Copy + Attributes;
149 /// An empty set.
150 const NONE: Self;
151
152 /// Adds an item to the first available slot. Returns `false` if the set is full or already
153 /// contains the value.
154 fn insert(&mut self, item: Self::Item) -> bool;
155
156 /// Same as `insert`, but returns a [`std::result::Result`] with descriptive error to identify
157 /// why the insert failed.
158 fn try_insert(&mut self, item: Self::Item) -> Result<(), InsertError>;
159
160 /// Adds an item to the first available slot. Replaces the last item in the set if the set is
161 /// full. Returns `false` if the set already contains the value.
162 fn insert_or_replace_last(&mut self, item: Self::Item) -> bool;
163
164 /// Removes an item from the set. Returns whether the value was present in the set.
165 fn remove(&mut self, item: &Self::Item) -> bool;
166
167 /// Removes and returns the item in the set, if any, that is equal to the given one.
168 fn take(&mut self, item: &Self::Item) -> Option<Self::Item>;
169
170 /// Replaces an item in the set with a new item. `false` if the item was not present.
171 fn replace(&mut self, item: &Self::Item, new_item: Self::Item) -> bool;
172
173 /// Clears the set.
174 fn clear(&mut self);
175
176 /// Gets an item from the set by index.
177 fn get(&self, index: usize) -> Option<&Self::Item> {
178 self.as_slice().get(index).and_then(|opt| opt.as_ref())
179 }
180
181 /// Returns the number of elements in the set.
182 #[inline]
183 fn len(&self) -> usize {
184 // The sets are small so iteration is fine.
185 self.as_slice()
186 .iter()
187 .filter(|x| x.is_some())
188 .count()
189 }
190
191 /// Returns true if the set contains the given item.
192 fn contains(&self, item: &Self::Item) -> bool {
193 self.as_slice().contains(&Some(*item))
194 }
195
196 /// Returns true if the set is empty.
197 #[inline]
198 fn is_empty(&self) -> bool {
199 self.len() == 0
200 }
201
202 /// Returns true if the set is full, i.e., it contains the maximum number of elements.
203 #[inline]
204 fn is_full(&self) -> bool {
205 self.len() == Self::MAX_COUNT
206 }
207
208 /// Gets the capacity of the set.
209 #[inline]
210 fn capacity(&self) -> usize {
211 Self::MAX_COUNT
212 }
213
214 /// Gets the first item in the set.
215 fn first(&self) -> Option<&Self::Item> {
216 self.iter().next()
217 }
218
219 /// Gets the last item in the set.
220 fn last(&self) -> Option<&Self::Item> {
221 self.iter().last()
222 }
223
224 /// Returns the items that are in `self` but not in `other`.
225 fn difference(&self, other: &Self) -> Self {
226 let mut result = Self::default();
227
228 for s in self.iter() {
229 if !other.contains(s) {
230 result.insert(*s);
231 }
232 }
233
234 result
235 }
236
237 /// Returns the items that are both in `self` and `other`.
238 fn intersection(&self, other: &Self) -> Self {
239 let mut result = Self::default();
240
241 for s in self.iter() {
242 if other.contains(s) {
243 result.insert(*s);
244 }
245 }
246
247 result
248 }
249
250 /// Returns `true` if `self` has no items in common with `other`. This is equivalent to
251 /// checking for an empty intersection.
252 fn is_disjoint(&self, other: &Self) -> bool {
253 self.intersection(other).is_empty()
254 }
255
256 /// Returns true if the set is a subset of another, i.e., other contains at least all the
257 /// values in self.
258 fn is_subset(&self, other: &Self) -> bool {
259 if self.len() > other.len() {
260 return false;
261 }
262
263 self.iter().all(|spell| other.contains(spell))
264 }
265
266 /// Returns true if the set is a superset of another, i.e., self contains at least all the
267 /// values in other.
268 fn is_superset(&self, other: &Self) -> bool {
269 other.is_subset(self)
270 }
271
272 /// Returns an iterator over the set.
273 #[inline]
274 fn iter(&self) -> impl Iterator<Item = &Self::Item> {
275 self.as_slice().iter().filter_map(|opt| opt.as_ref())
276 }
277
278 /// Returns a mutable iterator over the set.
279 fn iter_mut(&mut self) -> impl Iterator<Item = &mut Self::Item> {
280 self.as_mut_slice().iter_mut().filter_map(|opt| opt.as_mut())
281 }
282
283 /// Retains only the items specified by the predicate.
284 fn retain<F>(&mut self, mut f: F)
285 where
286 F: FnMut(&Self::Item) -> bool,
287 {
288 for slot in self.as_mut_slice() {
289 if let Some(ref item) = slot {
290 if !f(item) {
291 *slot = None;
292 }
293 }
294 }
295 }
296
297 /// Extends items from an iterator into the set.
298 fn extend<I: IntoIterator<Item = Self::Item>>(&mut self, iter: I) {
299 for item in iter {
300 self.insert(item);
301 }
302 }
303
304 /// Converts each element to an [`ItemAttribute`].
305 fn iter_attributes(&self) -> impl Iterator<Item = ItemAttribute>;
306
307 /// Returns the inner storage as a slice.
308 fn as_slice(&self) -> &[Option<Self::Item>];
309
310 /// Returns the inner storage as a mutable slice.
311 fn as_mut_slice(&mut self) -> &mut [Option<Self::Item>];
312}
313
314// Compilation optimization.
315// See: <https://matklad.github.io/2021/07/09/inline-in-rust.html>
316fn extract_color(s: &str) -> Option<u32> {
317 let len = s.len();
318 let mut color = s;
319
320 if len == 7 && color.starts_with('#') {
321 color = &color[1..len];
322 } else if len != 6 {
323 return None;
324 }
325
326 u32::from_str_radix(color, 16).ok()
327}