rosu_mods/
mods.rs

1use std::{
2    cmp::Ordering,
3    collections::BTreeMap,
4    fmt::{Debug, Display, Formatter, Result as FmtResult},
5    iter::FromIterator,
6    ops::BitOr,
7};
8
9use crate::{
10    generated_mods::{GameMod, GameModIntermode},
11    intersection::{GameModsIntersection, IntersectionInner},
12    iter::{GameModsIter, GameModsIterMut, IntoGameModsIter},
13    order::GameModOrder,
14    Acronym, GameMode, GameModsIntermode, GameModsLegacy,
15};
16
17/// Combination of [`GameMod`]s.
18#[derive(Clone, Default, PartialEq)]
19pub struct GameMods {
20    pub(crate) inner: BTreeMap<GameModOrder, GameMod>,
21}
22
23impl GameMods {
24    /// Returns empty mods i.e. "`NoMod`"
25    pub const fn new() -> Self {
26        Self {
27            inner: BTreeMap::new(),
28        }
29    }
30
31    /// Return the accumulated bit values of all contained mods.
32    ///
33    /// Mods that don't have bit values will be ignored.
34    /// See <https://github.com/ppy/osu-api/wiki#mods>
35    ///
36    /// # Example
37    /// ```rust
38    /// # use rosu_mods::{GameMod, GameMods};
39    /// # let hdhrdtwu: GameMods = [
40    /// #   GameMod::HiddenOsu(Default::default()),
41    /// #   GameMod::HardRockOsu(Default::default()),
42    /// #   GameMod::DoubleTimeOsu(Default::default()),
43    /// #   GameMod::WindUpOsu(Default::default()),
44    /// # ].into_iter().collect();
45    /// # /*
46    /// let hdhrdtwu = mods!(Osu: HD HR DT WU);
47    /// # */
48    /// assert_eq!(hdhrdtwu.bits(), 8 + 16 + 64);
49    /// ```
50    pub fn bits(&self) -> u32 {
51        self.inner
52            .values()
53            .filter_map(GameMod::bits)
54            .fold(0, u32::bitor)
55    }
56
57    /// Return the accumulated bit values of all contained mods.
58    ///
59    /// If any contained mod has no bit value `None` is returned.
60    /// See <https://github.com/ppy/osu-api/wiki#mods>
61    ///
62    /// # Example
63    /// ```rust
64    /// # use rosu_mods::{GameMod, GameMods};
65    /// # let hdhrdt: GameMods = [
66    /// #   GameMod::HiddenOsu(Default::default()),
67    /// #   GameMod::HardRockOsu(Default::default()),
68    /// #   GameMod::DoubleTimeOsu(Default::default()),
69    /// # ].into_iter().collect();
70    /// # let hdhrdtwu: GameMods = [
71    /// #   GameMod::HiddenOsu(Default::default()),
72    /// #   GameMod::HardRockOsu(Default::default()),
73    /// #   GameMod::DoubleTimeOsu(Default::default()),
74    /// #   GameMod::WindUpOsu(Default::default()),
75    /// # ].into_iter().collect();
76    /// # /*
77    /// let hdhrdt = mods!(Osu: HD HR DT);
78    /// # */
79    /// assert_eq!(hdhrdt.checked_bits(), Some(8 + 16 + 64));
80    ///
81    /// # /*
82    /// let hdhrdtwu = mods!(Osu: HD HR DT WU);
83    /// # */
84    /// assert_eq!(hdhrdtwu.checked_bits(), None);
85    /// ```
86    pub fn checked_bits(&self) -> Option<u32> {
87        self.inner
88            .values()
89            .map(GameMod::bits)
90            .try_fold(0, |bits, next| Some(next? | bits))
91    }
92
93    /// Returns `true` if no mods are contained.
94    ///
95    /// # Example
96    /// ```rust
97    /// use rosu_mods::{GameMod, GameMods};
98    ///
99    /// let mut mods = GameMods::new();
100    /// assert!(mods.is_empty());
101    ///
102    /// mods.insert(GameMod::HiddenOsu(Default::default()));
103    /// assert!(!mods.is_empty());
104    /// ```
105    pub fn is_empty(&self) -> bool {
106        self.inner.is_empty()
107    }
108
109    /// Returns the amount of contained mods.
110    ///
111    /// # Example
112    /// ```rust
113    /// use rosu_mods::{GameMod, GameMods};
114    ///
115    /// # let hdhrdt: GameMods = [
116    /// #   GameMod::HiddenCatch(Default::default()),
117    /// #   GameMod::HardRockCatch(Default::default()),
118    /// #   GameMod::DoubleTimeCatch(Default::default()),
119    /// # ].into_iter().collect();
120    /// # /*
121    /// let hdhrdt = mods!(Catch: HD HR DT);
122    /// # */
123    /// assert_eq!(hdhrdt.len(), 3);
124    ///
125    /// let mut nm = GameMods::new();
126    /// assert_eq!(nm.len(), 0);
127    /// assert_eq!(nm.to_string(), "NM");
128    /// ```
129    pub fn len(&self) -> usize {
130        self.inner.len()
131    }
132
133    /// Add a [`GameMod`]
134    ///
135    /// # Example
136    /// ```rust
137    /// use rosu_mods::{GameMod, GameMods};
138    ///
139    /// let mut mods = GameMods::new();
140    /// assert_eq!(mods.to_string(), "NM");
141    ///
142    /// mods.insert(GameMod::TraceableOsu(Default::default()));
143    /// assert_eq!(mods.to_string(), "TC");
144    ///
145    /// mods.insert(GameMod::HardRockOsu(Default::default()));
146    /// assert_eq!(mods.to_string(), "HRTC");
147    /// ```
148    pub fn insert(&mut self, gamemod: GameMod) {
149        self.inner.insert(GameModOrder::from(&gamemod), gamemod);
150    }
151
152    /// Check whether a given [`GameMod`] is contained.
153    ///
154    /// # Example
155    /// ```rust
156    /// use rosu_mods::GameMod;
157    ///
158    /// # let hd = rosu_mods::GameMods::from(GameMod::HiddenTaiko(Default::default()));
159    /// # /*
160    /// let hd = mods!(Taiko: HD);
161    /// # */
162    /// assert!(hd.contains(&GameMod::HiddenTaiko(Default::default())));
163    /// assert!(!hd.contains(&GameMod::HiddenOsu(Default::default())));
164    /// ```
165    pub fn contains(&self, gamemod: &GameMod) -> bool {
166        self.inner.contains_key(&GameModOrder::from(gamemod))
167    }
168
169    /// Check whether a given [`GameModIntermode`] is contained.
170    ///
171    /// # Example
172    /// ```rust
173    /// use rosu_mods::GameModIntermode;
174    ///
175    /// # let hd = rosu_mods::GameMods::from(rosu_mods::GameMod::HiddenTaiko(Default::default()));
176    /// # /*
177    /// let hd = mods!(Taiko: HD);
178    /// # */
179    /// assert!(hd.contains_intermode(GameModIntermode::Hidden));
180    /// assert!(!hd.contains_intermode(GameModIntermode::HardRock));
181    /// ```
182    pub fn contains_intermode<M>(&self, gamemod: M) -> bool
183    where
184        GameModIntermode: From<M>,
185    {
186        self.inner.contains_key(&GameModIntermode::from(gamemod))
187    }
188
189    /// Check whether any of the given mods are contained.
190    ///
191    /// Note that this method does not consider the mods' modes so it could
192    /// return `true` even if it's a different mode.
193    ///
194    /// # Example
195    /// ```rust
196    /// use rosu_mods::mods;
197    ///
198    /// # use rosu_mods::{GameMod, GameMods};
199    /// # let hd = GameMods::from(GameMod::HiddenTaiko(Default::default()));
200    /// # /*
201    /// let hd = mods!(Taiko: HD);
202    /// # */
203    ///
204    /// assert!(hd.contains_any(mods!(HD HR)));
205    /// assert!(!hd.contains_any(mods!(HR DT)));
206    ///
207    /// // Careful: It returns `true` even if it's a different mode
208    /// # assert!(hd.contains_any(GameMods::from(GameMod::HiddenOsu(Default::default()))));
209    /// # /*
210    /// assert!(hd.contains_any(mods!(Osu: HD)));
211    /// # */
212    /// ```
213    pub fn contains_any<I, M>(&self, mods: I) -> bool
214    where
215        I: IntoIterator<Item = M>,
216        GameModIntermode: From<M>,
217    {
218        mods.into_iter()
219            .any(|gamemod| self.contains_intermode(gamemod))
220    }
221
222    /// Check whether a given [`Acronym`] is contained.
223    ///
224    /// # Example
225    /// ```rust
226    /// use rosu_mods::Acronym;
227    ///
228    /// # use rosu_mods::{GameMod, GameMods};
229    /// # let mods: GameMods = [
230    /// #   GameMod::NoFailOsu(Default::default()),
231    /// #   GameMod::DoubleTimeOsu(Default::default()),
232    /// # ].into_iter().collect();
233    /// # /*
234    /// let mods = mods!(Osu: NF DT);
235    /// # */
236    ///
237    /// let nf = "NF".parse::<Acronym>().unwrap();
238    /// assert!(mods.contains_acronym(nf));
239    ///
240    /// let hd = "HD".parse::<Acronym>().unwrap();
241    /// assert!(!mods.contains_acronym(hd));
242    /// ```
243    pub fn contains_acronym(&self, acronym: Acronym) -> bool {
244        self.inner
245            .values()
246            .any(|gamemod| gamemod.acronym() == acronym)
247            || (self.is_empty() && acronym.as_str() == "NM")
248    }
249
250    /// Remove a [`GameMod`] and return whether it was contained.
251    ///
252    /// # Example
253    /// ```
254    /// use rosu_mods::{GameMod, GameMods};
255    ///
256    /// # let mut mods: GameMods = [
257    /// #   GameMod::DoubleTimeMania(Default::default()),
258    /// #   GameMod::MirrorMania(Default::default())
259    /// # ].into_iter().collect();
260    /// # /*
261    /// let mut mods: GameMods = mods!(Mania: DT MR);
262    /// #*/
263    ///
264    /// assert!(mods.remove(&GameMod::MirrorMania(Default::default())));
265    /// assert_eq!(mods.to_string(), "DT");
266    /// assert!(!mods.remove(&GameMod::DoubleTimeCatch(Default::default())));
267    /// ```
268    pub fn remove(&mut self, gamemod: &GameMod) -> bool {
269        self.inner.remove(&GameModOrder::from(gamemod)).is_some()
270    }
271
272    /// Remove a gamemod and return whether it was contained.
273    ///
274    /// If the same gamemod is contained for multiple modes, only one of them will be removed.
275    ///
276    /// # Example
277    /// ```
278    /// use rosu_mods::{mods, GameModIntermode, GameMod, GameMods};
279    ///
280    /// let mut mods: GameMods = [
281    ///     GameMod::HiddenOsu(Default::default()),
282    ///     GameMod::HiddenTaiko(Default::default()),
283    ///     GameMod::HardRockOsu(Default::default()),
284    /// ].into_iter().collect();
285    ///
286    /// assert_eq!(mods.to_string(), "HDHRHD");
287    ///
288    /// assert!(mods.remove_intermode(GameModIntermode::Hidden));
289    /// assert_eq!(mods.to_string(), "HRHD");
290    /// assert!(!mods.remove_intermode(GameModIntermode::DoubleTime));
291    /// ```
292    pub fn remove_intermode<M>(&mut self, gamemod: M) -> bool
293    where
294        GameModIntermode: From<M>,
295    {
296        self.inner
297            .remove(&GameModIntermode::from(gamemod))
298            .is_some()
299    }
300
301    /// Remove all mods contained in the iterator.
302    ///
303    /// # Example
304    /// ```
305    /// use rosu_mods::{mods, GameMod, GameMods};
306    ///
307    /// # let mut mods: GameMods = [
308    /// #   GameMod::HiddenOsu(Default::default()),
309    /// #   GameMod::HardRockOsu(Default::default()),
310    /// #   GameMod::WiggleOsu(Default::default()),
311    /// #   GameMod::DoubleTimeOsu(Default::default()),
312    /// #   GameMod::BarrelRollOsu(Default::default()),
313    /// # ].into_iter().collect();
314    /// # /*
315    /// let mut mods: GameMods = mods!(Osu: HD HR WG DT BR);
316    /// # */
317    ///
318    /// mods.remove_all([
319    ///     GameMod::HiddenOsu(Default::default()),
320    ///     GameMod::EasyOsu(Default::default())
321    /// ].iter());
322    /// assert_eq!(mods.to_string(), "DTHRBRWG");
323    ///
324    /// mods.remove_all(mods!(Osu: NF WG).iter());
325    /// assert_eq!(mods.to_string(), "DTHRBR")
326    /// ```
327    pub fn remove_all<'m, I>(&mut self, mods: I)
328    where
329        I: Iterator<Item = &'m GameMod>,
330    {
331        for gamemod in mods {
332            self.remove(gamemod);
333        }
334    }
335
336    /// Remove all mods contained in the iterator.
337    ///
338    /// If the same gamemod is contained for multiple modes, each occurence of the gamemod
339    /// in the iterator will remove only one of the contained gamemods.
340    ///
341    /// # Example
342    /// ```
343    /// use rosu_mods::{mods, GameMod, GameMods};
344    ///
345    ///  let mut mods: GameMods = [
346    ///    GameMod::HiddenOsu(Default::default()),
347    ///    GameMod::HardRockOsu(Default::default()),
348    ///    GameMod::HardRockCatch(Default::default()),
349    ///    GameMod::WiggleOsu(Default::default()),
350    ///    GameMod::DoubleTimeOsu(Default::default()),
351    ///    GameMod::BarrelRollOsu(Default::default()),
352    ///  ].into_iter().collect();
353    ///
354    /// assert_eq!(mods.to_string(), "DTHDHRBRWGHR");
355    /// mods.remove_all_intermode(mods!(HD HR WG));
356    /// assert_eq!(mods.to_string(), "DTBRHR");
357    /// ```
358    pub fn remove_all_intermode<I, M>(&mut self, mods: I)
359    where
360        I: IntoIterator<Item = M>,
361        GameModIntermode: From<M>,
362    {
363        for gamemod in mods {
364            self.remove_intermode(gamemod);
365        }
366    }
367
368    /// Returns an iterator over all mods that appear in both [`GameMods`].
369    ///
370    /// # Example
371    /// ```rust
372    /// use rosu_mods::GameMods;
373    ///
374    /// # use rosu_mods::GameMod;
375    /// # let hd = GameMods::from(GameMod::HiddenCatch(Default::default()));
376    /// # let hdhr: GameMods = [
377    /// #   GameMod::HiddenCatch(Default::default()),
378    /// #   GameMod::HardRockCatch(Default::default()),
379    /// # ].into_iter().collect();
380    /// # /*
381    /// let hd = mods!(Catch: HD);
382    /// let hdhr = mods!(Catch: HD HR);
383    /// # */
384    /// let mut intersection = hd.intersection(&hdhr);
385    ///
386    /// assert_eq!(intersection.next(), Some(&GameMod::HiddenCatch(Default::default())));
387    /// assert_eq!(intersection.next(), None);
388    /// ```
389    // https://github.com/rust-lang/rust/blob/c1d3610ac1ddd1cd605479274047fd0a3f37d220/library/alloc/src/collections/btree/set.rs#L517
390    pub fn intersection<'m>(&'m self, other: &'m GameMods) -> GameModsIntersection<'m> {
391        let (Some(self_min), Some(self_max)) =
392            (self.inner.first_key_value(), self.inner.last_key_value())
393        else {
394            return GameModsIntersection {
395                inner: IntersectionInner::Answer(None),
396            };
397        };
398
399        let (Some(other_min), Some(other_max)) =
400            (other.inner.first_key_value(), other.inner.last_key_value())
401        else {
402            return GameModsIntersection {
403                inner: IntersectionInner::Answer(None),
404            };
405        };
406
407        GameModsIntersection {
408            inner: match (self_min.0.cmp(other_max.0), self_max.0.cmp(other_min.0)) {
409                (Ordering::Greater, _) | (_, Ordering::Less) => IntersectionInner::Answer(None),
410                (Ordering::Equal, _) => IntersectionInner::Answer(Some(self_min.1)),
411                (_, Ordering::Equal) => IntersectionInner::Answer(Some(self_max.1)),
412                _ => IntersectionInner::new_stitch(self.inner.iter(), other.inner.iter()),
413            },
414        }
415    }
416
417    /// Check whether the two [`GameMods`] have any common mods.
418    ///
419    /// # Example
420    /// ```rust
421    /// use rosu_mods::GameMods;
422    ///
423    /// # use rosu_mods::GameMod;
424    /// # let hd = GameMods::from(GameMod::HiddenCatch(Default::default()));
425    /// # let hr = GameMods::from(GameMod::HardRockCatch(Default::default()));
426    /// # let hdhr: GameMods = [
427    /// #   GameMod::HiddenCatch(Default::default()),
428    /// #   GameMod::HardRockCatch(Default::default()),
429    /// # ].into_iter().collect();
430    /// # /*
431    /// let hd = mods!(Catch: HD);
432    /// let hr = mods!(Catch: HR);
433    /// # */
434    /// assert!(!hd.intersects(&hr));
435    ///
436    /// # /*
437    /// let hdhr = mods!(Catch: HD HR);
438    /// # */
439    /// assert!(hd.intersects(&hdhr));
440    /// ```
441    pub fn intersects(&self, other: &Self) -> bool {
442        self.intersection(other).next().is_some()
443    }
444
445    /// The clock rate of the [`GameMods`].
446    ///
447    /// Returns `None` if any contained [`GameMod`] has no single clock rate.
448    ///
449    /// # Example
450    /// ```rust
451    /// use rosu_mods::GameMod;
452    ///
453    /// # let hd: rosu_mods::GameMods = [GameMod::HiddenOsu(Default::default())].into_iter().collect();
454    /// # /*
455    /// let hd = mods!(Osu: HD);
456    /// # */
457    /// assert_eq!(hd.clock_rate(), Some(1.0));
458    ///
459    /// let mut hddt = hd;
460    /// hddt.insert(GameMod::DoubleTimeOsu(Default::default()));
461    /// assert_eq!(hddt.clock_rate(), Some(1.5));
462    ///
463    /// let mut hddtwu = hddt;
464    /// hddtwu.insert(GameMod::WindUpOsu(Default::default()));
465    /// assert_eq!(hddtwu.clock_rate(), None);
466    /// ```
467    pub fn clock_rate(&self) -> Option<f64> {
468        self.inner
469            .values()
470            .map(GameMod::clock_rate)
471            .try_fold(1.0, |clock_rate, next| next.map(|next| clock_rate * next))
472    }
473
474    /// Tries to create [`GameMods`] from a [`GameModsIntermode`].
475    ///
476    /// Returns `None` if any contained [`GameModIntermode`] is unknown for the
477    /// given [`GameMode`].
478    ///
479    /// # Example
480    /// ```rust
481    /// use rosu_mods::{mods, GameMods, GameModsIntermode, GameMode};
482    ///
483    /// let intermode: GameModsIntermode = mods!(DT FI);
484    /// let mods = GameMods::try_from_intermode(&intermode, GameMode::Mania).unwrap();
485    ///
486    /// // The FadeIn mod doesn't exist in Taiko
487    /// assert!(GameMods::try_from_intermode(&intermode, GameMode::Taiko).is_none());
488    /// ```
489    pub fn try_from_intermode(mods: &GameModsIntermode, mode: GameMode) -> Option<Self> {
490        mods.try_with_mode(mode)
491    }
492
493    /// Create [`GameMods`] from a [`GameModsIntermode`].
494    ///
495    /// Any contained [`GameModIntermode`] that's unknown for the given
496    /// [`GameMode`] will be replaced with `GameModIntermode::Unknown`.
497    ///
498    /// # Example
499    /// ```rust
500    /// use rosu_mods::{mods, GameMods, GameModsIntermode, GameMode};
501    ///
502    /// let intermode: GameModsIntermode = mods!(DT FI);
503    /// let mods = GameMods::from_intermode(&intermode, GameMode::Mania);
504    ///
505    /// // The FadeIn mod doesn't exist in Taiko
506    /// let dt = GameMods::from_intermode(&intermode, GameMode::Taiko);
507    /// ```
508    pub fn from_intermode(mods: &GameModsIntermode, mode: GameMode) -> Self {
509        mods.with_mode(mode)
510    }
511
512    /// Returns an iterator over all contained mods.
513    ///
514    /// Note that the iterator will immediately yield `None` in case of "`NoMod`".
515    pub fn iter(&self) -> GameModsIter<'_> {
516        GameModsIter::new(self.inner.values())
517    }
518
519    /// Returns an iterator that allows modifying each contained mod.
520    ///
521    /// Note that the iterator will immediately yield `None` in case of "`NoMod`".
522    pub fn iter_mut(&mut self) -> GameModsIterMut<'_> {
523        GameModsIterMut::new(self.inner.values_mut())
524    }
525
526    /// Checks whether some contained mods exclude other contained mods.
527    ///
528    /// # Example
529    /// ```rust
530    /// use rosu_mods::GameMod;
531    ///
532    /// # let mut mods: rosu_mods::GameMods = [
533    /// #   GameMod::EasyOsu(Default::default()),
534    /// # ].into_iter().collect();
535    /// # /*
536    /// let mut mods = mods!(Osu: EZ);
537    /// # */
538    /// assert!(mods.is_valid());
539    ///
540    /// mods.insert(GameMod::HardRockOsu(Default::default()));
541    /// assert!(!mods.is_valid());
542    /// ```
543    pub fn is_valid(&self) -> bool {
544        for gamemod in self.inner.values() {
545            for &acronym in gamemod.incompatible_mods().iter() {
546                if self.contains_acronym(acronym) {
547                    return false;
548                }
549            }
550        }
551
552        true
553    }
554
555    /// Remove all mods that are excluded by other contained mods.
556    ///
557    /// # Example
558    /// ```rust
559    /// # let mut mods: rosu_mods::GameMods = [
560    /// #   rosu_mods::GameMod::EasyOsu(Default::default()),
561    /// #   rosu_mods::GameMod::HardRockOsu(Default::default())
562    /// # ].into_iter().collect();
563    /// # /*
564    /// let mut mods = mods!(Osu: EZ HR);
565    /// # */
566    /// assert_eq!(mods.to_string(), "EZHR");
567    ///
568    /// mods.sanitize();
569    /// assert_eq!(mods.to_string(), "EZ");
570    /// ```
571    pub fn sanitize(&mut self) {
572        'outer: loop {
573            let mods = self.inner.values();
574
575            for gamemod in mods {
576                for &excluded in gamemod.incompatible_mods().iter() {
577                    let intermode = GameModIntermode::from_acronym(excluded);
578
579                    if self.contains_intermode(intermode) {
580                        self.inner.retain(|key, _| *key != intermode);
581
582                        continue 'outer;
583                    }
584                }
585            }
586
587            break;
588        }
589    }
590
591    /// Turns [`GameMods`] into [`GameModsLegacy`].
592    pub fn as_legacy(&self) -> GameModsLegacy {
593        GameModsLegacy::from_bits(self.bits())
594    }
595
596    /// Attempts to turns [`GameMods`] into [`GameModsLegacy`].
597    ///
598    /// Returns `None` if any contained [`GameMod`] does not have a bit value.
599    pub fn try_as_legacy(&self) -> Option<GameModsLegacy> {
600        self.checked_bits().map(GameModsLegacy::from_bits)
601    }
602}
603
604impl Debug for GameMods {
605    fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
606        f.debug_list().entries(self.inner.values()).finish()
607    }
608}
609
610impl Display for GameMods {
611    fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
612        if self.is_empty() {
613            f.write_str("NM")
614        } else {
615            for gamemod in self.iter() {
616                f.write_str(gamemod.acronym().as_str())?;
617            }
618
619            Ok(())
620        }
621    }
622}
623
624impl From<GameMod> for GameMods {
625    fn from(gamemod: GameMod) -> Self {
626        let mut mods = Self::new();
627        mods.insert(gamemod);
628
629        mods
630    }
631}
632
633impl IntoIterator for GameMods {
634    type Item = GameMod;
635    type IntoIter = IntoGameModsIter;
636
637    /// Turns [`GameMods`] into an iterator over all contained mods.
638    ///
639    /// Note that the iterator will immediately yield `None` in case of "`NoMod`".
640    fn into_iter(self) -> Self::IntoIter {
641        IntoGameModsIter::new(self.inner.into_values())
642    }
643}
644
645impl<'a> IntoIterator for &'a GameMods {
646    type Item = <GameModsIter<'a> as Iterator>::Item;
647    type IntoIter = GameModsIter<'a>;
648
649    fn into_iter(self) -> Self::IntoIter {
650        self.iter()
651    }
652}
653
654impl<'a> IntoIterator for &'a mut GameMods {
655    type Item = <GameModsIterMut<'a> as Iterator>::Item;
656    type IntoIter = GameModsIterMut<'a>;
657
658    fn into_iter(self) -> Self::IntoIter {
659        self.iter_mut()
660    }
661}
662
663impl FromIterator<GameMod> for GameMods {
664    fn from_iter<T: IntoIterator<Item = GameMod>>(iter: T) -> Self {
665        Self {
666            inner: iter
667                .into_iter()
668                .map(|gamemod| (GameModOrder::from(&gamemod), gamemod))
669                .collect(),
670        }
671    }
672}
673
674impl Extend<GameMod> for GameMods {
675    fn extend<T: IntoIterator<Item = GameMod>>(&mut self, iter: T) {
676        let iter = iter
677            .into_iter()
678            .map(|gamemod| (GameModOrder::from(&gamemod), gamemod));
679
680        self.inner.extend(iter);
681    }
682}
683
684#[cfg(feature = "serde")]
685#[cfg_attr(all(docsrs, not(doctest)), doc(cfg(feature = "serde")))]
686const _: () = {
687    use serde::ser::{Serialize, SerializeSeq, Serializer};
688
689    impl Serialize for GameMods {
690        fn serialize<S: Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
691            let mut s = s.serialize_seq(Some(self.inner.len()))?;
692
693            for gamemod in self.inner.values() {
694                s.serialize_element(gamemod)?;
695            }
696
697            s.end()
698        }
699    }
700};
701
702#[cfg(feature = "rkyv")]
703#[cfg_attr(all(docsrs, not(doctest)), doc(cfg(feature = "rkyv")))]
704const _: () = {
705    use rkyv::{
706        rancor::{Fallible, Source},
707        ser::{Allocator, Writer},
708        vec::{ArchivedVec, VecResolver},
709        Archive, Archived, Deserialize, Place, Serialize,
710    };
711
712    impl Archive for GameMods {
713        type Archived = Archived<Vec<GameMod>>;
714        type Resolver = VecResolver;
715
716        fn resolve(&self, resolver: Self::Resolver, out: Place<Self::Archived>) {
717            ArchivedVec::resolve_from_len(self.inner.len(), resolver, out);
718        }
719    }
720
721    impl<S: Fallible<Error: Source> + Allocator + Writer + ?Sized> Serialize<S> for GameMods {
722        fn serialize(&self, s: &mut S) -> Result<Self::Resolver, S::Error> {
723            ArchivedVec::serialize_from_iter::<GameMod, _, _>(self.inner.values(), s)
724        }
725    }
726
727    impl<D: Fallible + ?Sized> Deserialize<GameMods, D> for ArchivedVec<Archived<GameMod>> {
728        fn deserialize(&self, deserializer: &mut D) -> Result<GameMods, D::Error> {
729            self.iter().map(|m| m.deserialize(deserializer)).collect()
730        }
731    }
732};
733
734#[cfg(test)]
735mod tests {
736    use crate::generated_mods::DoubleTimeOsu;
737
738    use super::*;
739
740    #[test]
741    fn insert_valid() {
742        let mut mods = GameMods::new();
743        mods.insert(GameMod::HiddenOsu(Default::default()));
744        mods.insert(GameMod::HardRockOsu(Default::default()));
745
746        assert_eq!(mods.len(), 2);
747        assert_eq!(mods.to_string(), "HDHR");
748    }
749
750    #[test]
751    fn contains() {
752        let mods: GameMods = [
753            GameMod::HiddenOsu(Default::default()),
754            GameMod::HardRockOsu(Default::default()),
755            GameMod::NightcoreOsu(Default::default()),
756        ]
757        .into_iter()
758        .collect();
759        assert!(mods.contains_intermode(GameModIntermode::Nightcore));
760        assert!(mods.contains_intermode(GameModIntermode::Hidden));
761        assert!(!mods.contains_intermode(GameModIntermode::DoubleTime));
762    }
763
764    #[test]
765    fn checked_bits() {
766        let mods: GameMods = [
767            GameMod::HiddenOsu(Default::default()),
768            GameMod::TraceableOsu(Default::default()),
769            GameMod::DoubleTimeOsu(Default::default()),
770        ]
771        .into_iter()
772        .collect();
773
774        assert_eq!(mods.checked_bits(), None);
775    }
776
777    #[test]
778    fn unchecked_bits() {
779        let mods: GameMods = [
780            GameMod::TraceableOsu(Default::default()),
781            GameMod::DoubleTimeOsu(Default::default()),
782            GameMod::HiddenOsu(Default::default()),
783        ]
784        .into_iter()
785        .collect();
786
787        assert_eq!(mods.bits(), 72);
788    }
789
790    #[test]
791    fn intersection() {
792        let a: GameMods = [
793            GameMod::HiddenOsu(Default::default()),
794            GameMod::WindUpOsu(Default::default()),
795            GameMod::HardRockOsu(Default::default()),
796        ]
797        .into_iter()
798        .collect();
799
800        let b: GameMods = [
801            GameMod::WindUpOsu(Default::default()),
802            GameMod::ClassicOsu(Default::default()),
803            GameMod::HardRockOsu(Default::default()),
804        ]
805        .into_iter()
806        .collect();
807
808        let mut iter = a.intersection(&b);
809        assert_eq!(
810            iter.next().map(GameMod::intermode),
811            Some(GameModIntermode::HardRock)
812        );
813        assert_eq!(
814            iter.next().map(GameMod::intermode),
815            Some(GameModIntermode::WindUp)
816        );
817        assert_eq!(iter.next(), None);
818    }
819
820    #[test]
821    fn clock_rate_unaffected() {
822        let mods: GameMods = [
823            GameMod::HiddenOsu(Default::default()),
824            GameMod::HardRockOsu(Default::default()),
825            GameMod::WiggleOsu(Default::default()),
826        ]
827        .into_iter()
828        .collect();
829
830        assert_eq!(mods.clock_rate(), Some(1.0));
831    }
832
833    #[test]
834    fn clock_rate_speed_change() {
835        let mut mods: GameMods = [GameMod::HardRockOsu(Default::default())]
836            .into_iter()
837            .collect();
838
839        mods.insert(GameMod::DoubleTimeOsu(DoubleTimeOsu {
840            speed_change: Some(1.25),
841            adjust_pitch: Some(false),
842        }));
843        assert_eq!(mods.clock_rate(), Some(1.25));
844    }
845
846    #[test]
847    fn clock_rate_variable() {
848        let mods: GameMods = [
849            GameMod::HiddenOsu(Default::default()),
850            GameMod::WindUpOsu(Default::default()),
851        ]
852        .into_iter()
853        .collect();
854
855        assert_eq!(mods.clock_rate(), None);
856    }
857
858    #[test]
859    fn sanitize() {
860        let mut mods: GameMods = [
861            GameMod::BlindsOsu(Default::default()),
862            GameMod::FlashlightOsu(Default::default()),
863            GameMod::HiddenOsu(Default::default()),
864            GameMod::TraceableOsu(Default::default()),
865        ]
866        .into_iter()
867        .collect();
868
869        mods.sanitize();
870
871        assert_eq!(mods.to_string(), "BLHD");
872    }
873}