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}