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
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
use crate::{AttributeDef, AttributeValue, ItemAttribute};
use crate::error::InsertError;
/// Attribute values for an item attribute.
pub trait Attribute: Sized {
/// The defindex.
const DEFINDEX: u32;
/// The attribute definition.
const ATTRIBUTE: AttributeDef;
/// **Not part of the schema.**
///
/// This is a marker to specify which attribute field is meaningful to us in obtaining the
/// attribute's value.
///
/// # Kill Count Example
/// ```json
/// {
/// "defindex": 214,
/// "value": 918,
/// "float_value": 1.28639199025018207e-42
/// }
/// ```
/// This is the "kill_eater" attribute. "918" refers to the number of kills. The `float_value`
/// field is the same number as a 32-bit float.
///
/// You can perform the conversions yourself with the following code:
/// ```
/// let value = 918u32;
/// let float_value = 1.28639199025018207e-42f32;
///
/// assert_eq!(f32::from_bits(value), float_value);
/// assert_eq!(float_value.to_bits(), value);
/// ```
///
/// # Sheen Example
/// ```json
/// {
/// "defindex": 2014,
/// "value": 1086324736,
/// "float_value": 6
/// }
/// ```
/// This is the "killstreak_idleeffect" attribute. "6" refers to the associated sheen (
/// [`Sheen::VillainousViolet`][`crate::Sheen::VillainousViolet`]), but is stored in the
/// `float_value` field, unlike "kill_eater". The `value` field is the same number as a 32-bit
/// float.
///
/// While both values refer to the same value, and internally the attribute's value is its
/// `float_value`, there are many cases where the `float_value` doesn't mean anything to us
/// unless converted to a 32-bit integer from its bits, and if the `float_value` does mean
/// something to us we don't want to convert it to an integer. This can be a little confusing,
/// but it's just how the API is.
///
/// By marking each attribute with a `uses_float_value` flag, we can indicate whether the
/// `float_value` field is meaningful to use for that attribute.
const USES_FLOAT_VALUE: bool;
/// Gets the attribute value.
fn attribute_value(&self) -> AttributeValue {
self.attribute_float_value()
.map(|v| v.to_bits().into())
.unwrap_or_default()
}
/// Gets the attribute float value.
fn attribute_float_value(&self) -> Option<f32>;
}
/// Associated attribute values for a set of item attributes.
pub trait Attributes: Sized {
/// The list of associated defindexes.
const DEFINDEX: &'static [u32];
/// The attribute definition.
const ATTRIBUTES: &'static [AttributeDef];
/// See [`Attribute::USES_FLOAT_VALUE`]. This applies to all attributes in the set.
const USES_FLOAT_VALUE: bool;
/// Gets the attribute value.
fn attribute_value(&self) -> AttributeValue {
self.attribute_float_value()
.map(|v| v.to_bits().into())
.unwrap_or_default()
}
/// Gets the attribute float value.
fn attribute_float_value(&self) -> Option<f32> {
None
}
/// Gets the attribute definition for a given defindex.
fn get_attribute_def_by_defindex(defindex: u32) -> Option<&'static AttributeDef> {
Self::ATTRIBUTES.iter().find(|attr| attr.defindex == defindex)
}
}
/// Backwards conversion for attributes associated with an integer value.
pub trait TryFromIntAttributeValue: Sized + TryFrom<u32> {
/// Attempts conversion from an attribute value.
#[allow(unused_variables)]
fn try_from_attribute_value(v: AttributeValue) -> Option<Self> {
None
}
/// Attempts conversion from an attribute float value.
fn try_from_attribute_float_value(v: f32) -> Option<Self> {
if v.fract() != 0.0 || v.is_sign_negative() || v > (u32::MAX as f32) {
return None;
}
Self::try_from(v as u32).ok()
}
}
/// Definitions which are associated with colors.
pub trait Colored: Sized {
/// Gets the color.
fn color(&self) -> u32;
/// Attempts to convert a hexadecimal color.
fn from_color(color: u32) -> Option<Self>;
/// Converts this into a hexademical color string in the format "#FFFFFF".
#[inline]
fn color_string(&self) -> String {
format!("#{:06X}", self.color())
}
/// Attempts to convert a hexadecimal color string.
fn from_color_str<S: AsRef<str>>(color: S) -> Option<Self> {
Self::from_color(extract_color(color.as_ref())?)
}
}
/// Definitions which are associated with an item defindex.
pub trait HasItemDefindex: Sized {
/// Gets the `defindex`.
fn defindex(&self) -> u32;
/// Converts a `defindex` into its related item, if it exists.
fn from_defindex(defindex: u32) -> Option<Self>;
}
/// A fixed set of attributes.
pub trait AttributeSet: Sized + Default {
/// Max number of items.
const MAX_COUNT: usize;
/// The item type.
type Item: PartialEq + Copy + Attributes;
/// An empty set.
const NONE: Self;
/// Adds an item to the first available slot. Returns `false` if the set is full or already
/// contains the value.
fn insert(&mut self, item: Self::Item) -> bool;
/// Same as `insert`, but returns a [`std::result::Result`] with descriptive error to identify
/// why the insert failed.
fn try_insert(&mut self, item: Self::Item) -> Result<(), InsertError>;
/// Adds an item to the first available slot. Replaces the last item in the set if the set is
/// full. Returns `false` if the set already contains the value.
fn insert_or_replace_last(&mut self, item: Self::Item) -> bool;
/// Removes an item from the set. Returns whether the value was present in the set.
fn remove(&mut self, item: &Self::Item) -> bool;
/// Removes and returns the item in the set, if any, that is equal to the given one.
fn take(&mut self, item: &Self::Item) -> Option<Self::Item>;
/// Replaces an item in the set with a new item. `false` if the item was not present.
fn replace(&mut self, item: &Self::Item, new_item: Self::Item) -> bool;
/// Clears the set.
fn clear(&mut self);
/// Gets an item from the set by index.
fn get(&self, index: usize) -> Option<&Self::Item> {
self.as_slice().get(index).and_then(|opt| opt.as_ref())
}
/// Returns the number of elements in the set.
#[inline]
fn len(&self) -> usize {
// The sets are small so iteration is fine.
self.as_slice()
.iter()
.filter(|x| x.is_some())
.count()
}
/// Returns true if the set contains the given item.
fn contains(&self, item: &Self::Item) -> bool {
self.as_slice().contains(&Some(*item))
}
/// Returns true if the set is empty.
#[inline]
fn is_empty(&self) -> bool {
self.len() == 0
}
/// Returns true if the set is full, i.e., it contains the maximum number of elements.
#[inline]
fn is_full(&self) -> bool {
self.len() == Self::MAX_COUNT
}
/// Gets the capacity of the set.
#[inline]
fn capacity(&self) -> usize {
Self::MAX_COUNT
}
/// Gets the first item in the set.
fn first(&self) -> Option<&Self::Item> {
self.iter().next()
}
/// Gets the last item in the set.
fn last(&self) -> Option<&Self::Item> {
self.iter().last()
}
/// Returns the items that are in `self` but not in `other`.
fn difference(&self, other: &Self) -> Self {
let mut result = Self::default();
for s in self.iter() {
if !other.contains(s) {
result.insert(*s);
}
}
result
}
/// Returns the items that are both in `self` and `other`.
fn intersection(&self, other: &Self) -> Self {
let mut result = Self::default();
for s in self.iter() {
if other.contains(s) {
result.insert(*s);
}
}
result
}
/// Returns `true` if `self` has no items in common with `other`. This is equivalent to
/// checking for an empty intersection.
fn is_disjoint(&self, other: &Self) -> bool {
self.intersection(other).is_empty()
}
/// Returns true if the set is a subset of another, i.e., other contains at least all the
/// values in self.
fn is_subset(&self, other: &Self) -> bool {
if self.len() > other.len() {
return false;
}
self.iter().all(|spell| other.contains(spell))
}
/// Returns true if the set is a superset of another, i.e., self contains at least all the
/// values in other.
fn is_superset(&self, other: &Self) -> bool {
other.is_subset(self)
}
/// Returns an iterator over the set.
#[inline]
fn iter(&self) -> impl Iterator<Item = &Self::Item> {
self.as_slice().iter().filter_map(|opt| opt.as_ref())
}
/// Returns a mutable iterator over the set.
fn iter_mut(&mut self) -> impl Iterator<Item = &mut Self::Item> {
self.as_mut_slice().iter_mut().filter_map(|opt| opt.as_mut())
}
/// Retains only the items specified by the predicate.
fn retain<F>(&mut self, mut f: F)
where
F: FnMut(&Self::Item) -> bool,
{
for slot in self.as_mut_slice() {
if let Some(ref item) = slot {
if !f(item) {
*slot = None;
}
}
}
}
/// Extends items from an iterator into the set.
fn extend<I: IntoIterator<Item = Self::Item>>(&mut self, iter: I) {
for item in iter {
self.insert(item);
}
}
/// Converts each element to an [`ItemAttribute`].
fn iter_attributes(&self) -> impl Iterator<Item = ItemAttribute>;
/// Returns the inner storage as a slice.
fn as_slice(&self) -> &[Option<Self::Item>];
/// Returns the inner storage as a mutable slice.
fn as_mut_slice(&mut self) -> &mut [Option<Self::Item>];
}
// Compilation optimization.
// See: <https://matklad.github.io/2021/07/09/inline-in-rust.html>
fn extract_color(s: &str) -> Option<u32> {
let len = s.len();
let mut color = s;
if len == 7 && color.starts_with('#') {
color = &color[1..len];
} else if len != 6 {
return None;
}
u32::from_str_radix(color, 16).ok()
}