rosu_mods/
legacy.rs

1use std::{
2    fmt::{Binary, Display, Formatter, Result as FmtResult},
3    ops::{BitAnd, BitAndAssign, BitOr, BitOrAssign, BitXor, Sub, SubAssign},
4    str::FromStr,
5};
6
7use crate::{
8    error::GameModsLegacyParseError, iter::GameModsLegacyIter, util, Acronym, GameModsIntermode,
9};
10
11/// Lightweight bitflag type for legacy mods.
12///
13/// # Example
14/// ```
15/// use rosu_mods::GameModsLegacy;
16///
17/// let nomod = GameModsLegacy::default();
18/// assert_eq!(nomod, GameModsLegacy::NoMod);
19///
20/// // Created via bit operations or from a u32
21/// let hdhr_1 = GameModsLegacy::Hidden | GameModsLegacy::HardRock;
22/// let hdhr_2 = GameModsLegacy::from_bits(8 + 16);
23/// assert_eq!(hdhr_1, hdhr_2);
24///
25/// // Various methods for convenience like `contains` and `intersects`.
26/// let ezhdpf = GameModsLegacy::Easy | GameModsLegacy::Hidden | GameModsLegacy::Perfect;
27/// assert!(!ezhdpf.contains(GameModsLegacy::HardRock));
28/// let hdpf = GameModsLegacy::Hidden | GameModsLegacy::Perfect;
29/// assert!(ezhdpf.intersects(hdpf));
30///
31/// // Parsing a `&str`
32/// let hdhrdt = "dthdhr".parse::<GameModsLegacy>().unwrap();
33/// assert_eq!(hdhrdt.bits(), 8 + 16 + 64);
34///
35/// // The Display implementation combines all acronyms
36/// assert_eq!(hdhrdt.to_string(), "HDHRDT".to_string());
37///
38/// // Has an iterator type
39/// let mut iter = GameModsLegacy::from_bits(536871512).iter();
40/// assert_eq!(iter.next(), Some(GameModsLegacy::Hidden));
41/// assert_eq!(iter.next(), Some(GameModsLegacy::HardRock));
42/// assert_eq!(iter.next(), Some(GameModsLegacy::Nightcore));
43/// assert_eq!(iter.next(), Some(GameModsLegacy::ScoreV2));
44/// assert_eq!(iter.next(), None);
45/// ```
46#[derive(Copy, Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
47#[repr(transparent)]
48pub struct GameModsLegacy(u32);
49
50#[allow(non_upper_case_globals)]
51impl GameModsLegacy {
52    pub const NoMod: Self = Self::from_bits_retain(0);
53    pub const NoFail: Self = Self::from_bits_retain(1 << 0);
54    pub const Easy: Self = Self::from_bits_retain(1 << 1);
55    pub const TouchDevice: Self = Self::from_bits_retain(1 << 2);
56    pub const Hidden: Self = Self::from_bits_retain(1 << 3);
57    pub const HardRock: Self = Self::from_bits_retain(1 << 4);
58    pub const SuddenDeath: Self = Self::from_bits_retain(1 << 5);
59    pub const DoubleTime: Self = Self::from_bits_retain(1 << 6);
60    pub const Relax: Self = Self::from_bits_retain(1 << 7);
61    pub const HalfTime: Self = Self::from_bits_retain(1 << 8);
62    pub const Nightcore: Self = Self::from_bits_retain((1 << 9) | Self::DoubleTime.bits());
63    pub const Flashlight: Self = Self::from_bits_retain(1 << 10);
64    pub const Autoplay: Self = Self::from_bits_retain(1 << 11);
65    pub const SpunOut: Self = Self::from_bits_retain(1 << 12);
66    pub const Autopilot: Self = Self::from_bits_retain(1 << 13);
67    pub const Perfect: Self = Self::from_bits_retain((1 << 14) | Self::SuddenDeath.bits());
68    pub const Key4: Self = Self::from_bits_retain(1 << 15);
69    pub const Key5: Self = Self::from_bits_retain(1 << 16);
70    pub const Key6: Self = Self::from_bits_retain(1 << 17);
71    pub const Key7: Self = Self::from_bits_retain(1 << 18);
72    pub const Key8: Self = Self::from_bits_retain(1 << 19);
73    pub const FadeIn: Self = Self::from_bits_retain(1 << 20);
74    pub const Random: Self = Self::from_bits_retain(1 << 21);
75    pub const Cinema: Self = Self::from_bits_retain(1 << 22);
76    pub const Target: Self = Self::from_bits_retain(1 << 23);
77    pub const Key9: Self = Self::from_bits_retain(1 << 24);
78    pub const KeyCoop: Self = Self::from_bits_retain(1 << 25);
79    pub const Key1: Self = Self::from_bits_retain(1 << 26);
80    pub const Key3: Self = Self::from_bits_retain(1 << 27);
81    pub const Key2: Self = Self::from_bits_retain(1 << 28);
82    pub const ScoreV2: Self = Self::from_bits_retain(1 << 29);
83    pub const Mirror: Self = Self::from_bits_retain(1 << 30);
84}
85
86impl GameModsLegacy {
87    /// Returns the clock rate for the mods i.e. 1.5 for DT, 0.75 for HT,
88    /// and 1.0 otherwise.
89    pub const fn clock_rate(self) -> f64 {
90        if self.contains(Self::DoubleTime) {
91            1.5
92        } else if self.contains(Self::HalfTime) {
93            0.75
94        } else {
95            1.0
96        }
97    }
98
99    /// Returns the amount of contained mods.
100    ///
101    /// # Example
102    /// ```
103    /// use rosu_mods::GameModsLegacy;
104    ///
105    /// assert_eq!(GameModsLegacy::NoMod.len(), 0);
106    /// let mods = GameModsLegacy::from_bits(8 + 16 + 64 + 128);
107    /// assert_eq!(mods.len(), 4);
108    /// ```
109    pub const fn len(self) -> usize {
110        self.bits().count_ones() as usize
111            - self.contains(Self::Nightcore) as usize
112            - self.contains(Self::Perfect) as usize
113    }
114
115    /// Returns an iterator over [`GameModsLegacy`].
116    ///
117    /// # Example
118    /// ```
119    /// use rosu_mods::GameModsLegacy;
120    ///
121    /// let mut iter = GameModsLegacy::from_bits(8 + 16 + 64 + 128).iter();
122    /// assert_eq!(iter.next(), Some(GameModsLegacy::Hidden));
123    /// assert_eq!(iter.next(), Some(GameModsLegacy::HardRock));
124    /// assert_eq!(iter.next(), Some(GameModsLegacy::DoubleTime));
125    /// assert_eq!(iter.next(), Some(GameModsLegacy::Relax));
126    /// assert_eq!(iter.next(), None);
127    ///
128    /// let mut iter = GameModsLegacy::NoMod.iter();
129    /// assert_eq!(iter.next(), Some(GameModsLegacy::NoMod));
130    /// assert_eq!(iter.next(), None);
131    /// ```
132    pub fn iter(self) -> GameModsLegacyIter {
133        self.into_iter()
134    }
135
136    /// Convert [`GameModsLegacy`] to [`GameModsIntermode`].
137    pub fn to_intermode(self) -> GameModsIntermode {
138        GameModsIntermode::from_bits(self.bits())
139    }
140}
141
142impl GameModsLegacy {
143    const fn all() -> Self {
144        Self::from_bits_retain(u32::MAX >> 2)
145    }
146
147    /// Get the underlying bits value.
148    ///
149    /// The returned value is exactly the bits set in this flags value.
150    pub const fn bits(self) -> u32 {
151        self.0
152    }
153
154    /// Convert from a bits value.
155    ///
156    /// This method will return `None` if any unknown bits are set.
157    pub const fn try_from_bits(bits: u32) -> Option<Self> {
158        if Self::from_bits(bits).bits() == bits {
159            Some(Self::from_bits_retain(bits))
160        } else {
161            None
162        }
163    }
164
165    /// Convert from a bits value, unsetting any unknown bits.
166    pub const fn from_bits(bits: u32) -> Self {
167        Self::from_bits_retain(bits & Self::all().bits())
168    }
169
170    /// Convert from a bits value exactly.
171    ///
172    /// Unknown bits are retained.
173    pub const fn from_bits_retain(bits: u32) -> Self {
174        Self(bits)
175    }
176
177    /// Whether all bits in this flags value are unset.
178    pub const fn is_empty(self) -> bool {
179        self.bits() == Self::NoMod.bits()
180    }
181
182    /// Whether any set bits in a source flags value are also set in a target flags value.
183    pub const fn intersects(self, other: Self) -> bool {
184        self.bits() & other.bits() != 0
185    }
186
187    /// Whether all set bits in a source flags value are also set in a target flags value.
188    pub const fn contains(self, other: Self) -> bool {
189        self.bits() & other.bits() == other.bits()
190    }
191
192    /// The bitwise or (`|`) of the bits in two flags values.
193    pub const fn insert(&mut self, other: Self) {
194        *self = Self::from_bits_retain(self.bits()).union(other);
195    }
196
197    /// The intersection of a source flags value with the complement of a target flags value (`&!`).
198    ///
199    /// This method is not equivalent to `self & !other` when `other` has unknown bits set.
200    /// `remove` won't truncate `other`, but the `!` operator will.
201    pub const fn remove(&mut self, other: Self) {
202        *self = Self::from_bits_retain(self.bits()).difference(other);
203    }
204
205    /// The bitwise and (`&`) of the bits in two flags values.
206    #[must_use]
207    pub const fn intersection(self, other: Self) -> Self {
208        Self::from_bits_retain(self.bits() & other.bits())
209    }
210
211    /// The bitwise or (`|`) of the bits in two flags values.
212    #[must_use]
213    pub const fn union(self, other: Self) -> Self {
214        Self::from_bits_retain(self.bits() | other.bits())
215    }
216
217    /// The intersection of a source flags value with the complement of a target flags value (`&!`).
218    ///
219    /// This method is not equivalent to `self & !other` when `other` has unknown bits set.
220    /// `difference` won't truncate `other`, but the `!` operator will.
221    #[must_use]
222    pub const fn difference(self, other: Self) -> Self {
223        Self::from_bits_retain(self.bits() & !other.bits())
224    }
225
226    /// The bitwise exclusive-or (`^`) of the bits in two flags values.
227    #[must_use]
228    pub const fn symmetric_difference(self, other: Self) -> Self {
229        Self::from_bits_retain(self.bits() ^ other.bits())
230    }
231}
232
233impl Display for GameModsLegacy {
234    fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
235        for m in self.into_iter() {
236            let acronym = match m {
237                Self::NoMod => "NM",
238                Self::NoFail => "NF",
239                Self::Easy => "EZ",
240                Self::TouchDevice => "TD",
241                Self::Hidden => "HD",
242                Self::HardRock => "HR",
243                Self::SuddenDeath => "SD",
244                Self::DoubleTime => "DT",
245                Self::Relax => "RX",
246                Self::HalfTime => "HT",
247                Self::Nightcore => "NC",
248                Self::Flashlight => "FL",
249                Self::SpunOut => "SO",
250                Self::Autopilot => "AP",
251                Self::Perfect => "PF",
252                Self::FadeIn => "FI",
253                Self::Random => "RD",
254                Self::Target => "TP",
255                Self::ScoreV2 => "V2",
256                Self::Mirror => "MR",
257                Self::Key1 => "1K",
258                Self::Key2 => "2K",
259                Self::Key3 => "3K",
260                Self::Key4 => "4K",
261                Self::Key5 => "5K",
262                Self::Key6 => "6K",
263                Self::Key7 => "7K",
264                Self::Key8 => "8K",
265                Self::Key9 => "9K",
266                Self::Autoplay | Self::Cinema | Self::KeyCoop => "",
267                _ => unreachable!(),
268            };
269
270            f.write_str(acronym)?;
271        }
272
273        Ok(())
274    }
275}
276
277impl Binary for GameModsLegacy {
278    fn fmt(&self, f: &mut Formatter) -> FmtResult {
279        Binary::fmt(&self.0, f)
280    }
281}
282
283impl BitOr for GameModsLegacy {
284    type Output = Self;
285
286    /// The bitwise or (`|`) of the bits in two flags values.
287    fn bitor(self, other: GameModsLegacy) -> Self {
288        self.union(other)
289    }
290}
291
292impl BitOrAssign for GameModsLegacy {
293    /// The bitwise or (`|`) of the bits in two flags values.
294    fn bitor_assign(&mut self, other: Self) {
295        self.insert(other);
296    }
297}
298
299impl BitXor for GameModsLegacy {
300    type Output = Self;
301
302    /// The bitwise exclusive-or (`^`) of the bits in two flags values.
303    fn bitxor(self, other: Self) -> Self {
304        self.symmetric_difference(other)
305    }
306}
307
308impl BitAnd for GameModsLegacy {
309    type Output = Self;
310
311    /// The bitwise and (`&`) of the bits in two flags values.
312    fn bitand(self, other: Self) -> Self {
313        self.intersection(other)
314    }
315}
316
317impl BitAndAssign for GameModsLegacy {
318    /// The bitwise and (`&`) of the bits in two flags values.
319    fn bitand_assign(&mut self, other: Self) {
320        *self = Self::from_bits_retain(self.bits()).intersection(other);
321    }
322}
323
324impl Sub for GameModsLegacy {
325    type Output = Self;
326
327    /// The intersection of a source flags value with the complement of a target flags value (`&!`).
328    ///
329    /// This method is not equivalent to `self & !other` when `other` has unknown bits set.
330    /// `difference` won't truncate `other`, but the `!` operator will.
331    fn sub(self, other: Self) -> Self {
332        self.difference(other)
333    }
334}
335
336impl SubAssign for GameModsLegacy {
337    /// The intersection of a source flags value with the complement of a target flags value (`&!`).
338    ///
339    /// This method is not equivalent to `self & !other` when `other` has unknown bits set.
340    /// `difference` won't truncate `other`, but the `!` operator will.
341    fn sub_assign(&mut self, other: Self) {
342        self.remove(other);
343    }
344}
345
346impl Extend<GameModsLegacy> for GameModsLegacy {
347    /// The bitwise or (`|`) of the bits in each flags value.
348    fn extend<T: IntoIterator<Item = Self>>(&mut self, iterator: T) {
349        for item in iterator {
350            self.insert(item);
351        }
352    }
353}
354
355impl FromIterator<GameModsLegacy> for GameModsLegacy {
356    /// The bitwise or (`|`) of the bits in each flags value.
357    fn from_iter<T: IntoIterator<Item = Self>>(iterator: T) -> Self {
358        let mut mods = Self::NoMod;
359        mods.extend(iterator);
360
361        mods
362    }
363}
364
365impl From<GameModsLegacy> for u32 {
366    fn from(mods: GameModsLegacy) -> Self {
367        mods.bits()
368    }
369}
370
371impl FromStr for GameModsLegacy {
372    type Err = GameModsLegacyParseError;
373
374    fn from_str(s: &str) -> Result<Self, Self::Err> {
375        let mut res = Self::default();
376        let upper = util::to_uppercase(s);
377
378        for m in util::cut(&upper, 2) {
379            let m = match m {
380                "NM" => Self::NoMod,
381                "NF" => Self::NoFail,
382                "EZ" => Self::Easy,
383                "TD" => Self::TouchDevice,
384                "HD" => Self::Hidden,
385                "HR" => Self::HardRock,
386                "SD" => Self::SuddenDeath,
387                "DT" => Self::DoubleTime,
388                "RX" | "RL" => Self::Relax,
389                "HT" => Self::HalfTime,
390                "NC" => Self::Nightcore,
391                "FL" => Self::Flashlight,
392                "SO" => Self::SpunOut,
393                "AP" => Self::Autopilot,
394                "PF" => Self::Perfect,
395                "FI" => Self::FadeIn,
396                "RD" => Self::Random,
397                "TP" => Self::Target,
398                "V2" => Self::ScoreV2,
399                "MR" => Self::Mirror,
400                "1K" | "K1" => Self::Key1,
401                "2K" | "K2" => Self::Key2,
402                "3K" | "K3" => Self::Key3,
403                "4K" | "K4" => Self::Key4,
404                "5K" | "K5" => Self::Key5,
405                "6K" | "K6" => Self::Key6,
406                "7K" | "K7" => Self::Key7,
407                "8K" | "K8" => Self::Key8,
408                "9K" | "K9" => Self::Key9,
409                _ => return Err(GameModsLegacyParseError { mods: Box::from(s) }),
410            };
411
412            res.insert(m);
413        }
414
415        Ok(res)
416    }
417}
418
419impl TryFrom<Acronym> for GameModsLegacy {
420    type Error = GameModsLegacyParseError;
421
422    fn try_from(acronym: Acronym) -> Result<Self, Self::Error> {
423        acronym.as_str().parse()
424    }
425}
426
427impl IntoIterator for GameModsLegacy {
428    type Item = GameModsLegacy;
429    type IntoIter = GameModsLegacyIter;
430
431    fn into_iter(self) -> Self::IntoIter {
432        GameModsLegacyIter::new(self)
433    }
434}
435
436#[cfg(feature = "serde")]
437#[cfg_attr(all(docsrs, not(doctest)), doc(cfg(feature = "serde")))]
438const _: () = {
439    use serde::{
440        de::{Deserialize, Deserializer, Error as DeError, SeqAccess, Visitor},
441        ser::{Serialize, Serializer},
442    };
443
444    use crate::serde::{GameModRaw, GameModRawSeed, MaybeOwnedStr, BITFLAGS_U32};
445
446    impl<'de> Deserialize<'de> for GameModsLegacy {
447        fn deserialize<D: Deserializer<'de>>(d: D) -> Result<Self, D::Error> {
448            struct GameModsLegacyVisitor;
449
450            impl<'de> Visitor<'de> for GameModsLegacyVisitor {
451                type Value = GameModsLegacy;
452
453                fn expecting(&self, f: &mut Formatter<'_>) -> FmtResult {
454                    f.write_str("integer bitflags, mod acronyms, or a sequence of mods")
455                }
456
457                fn visit_i64<E: DeError>(self, v: i64) -> Result<Self::Value, E> {
458                    let bits = u32::try_from(v).map_err(|_| DeError::custom(BITFLAGS_U32))?;
459
460                    self.visit_u32(bits)
461                }
462
463                fn visit_u32<E: DeError>(self, v: u32) -> Result<Self::Value, E> {
464                    Ok(GameModsLegacy::from_bits(v))
465                }
466
467                fn visit_u64<E: DeError>(self, v: u64) -> Result<Self::Value, E> {
468                    let bits = u32::try_from(v).map_err(|_| DeError::custom(BITFLAGS_U32))?;
469
470                    self.visit_u32(bits)
471                }
472
473                fn visit_str<E: DeError>(self, v: &str) -> Result<Self::Value, E> {
474                    v.parse().map_err(DeError::custom)
475                }
476
477                fn visit_seq<A: SeqAccess<'de>>(self, mut seq: A) -> Result<Self::Value, A::Error> {
478                    let mut mods = GameModsLegacy::NoMod;
479
480                    let seed = GameModRawSeed {
481                        deny_unknown_fields: true,
482                    };
483
484                    while let Some(raw) = seq.next_element_seed(seed)? {
485                        fn try_acronym_to_gamemod<E: DeError>(
486                            acronym: &MaybeOwnedStr<'_>,
487                        ) -> Result<GameModsLegacy, E> {
488                            GameModsLegacy::from_str(acronym.as_str()).map_err(DeError::custom)
489                        }
490
491                        let gamemod = match raw {
492                            GameModRaw::Bits(bits) => GameModsLegacy::from_bits(bits),
493                            GameModRaw::Acronym(acronym) => try_acronym_to_gamemod(&acronym)?,
494                            GameModRaw::Full { acronym, .. } => try_acronym_to_gamemod(&acronym)?,
495                        };
496
497                        mods |= gamemod;
498                    }
499
500                    Ok(mods)
501                }
502            }
503
504            d.deserialize_any(GameModsLegacyVisitor)
505        }
506    }
507
508    impl Serialize for GameModsLegacy {
509        fn serialize<S: Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
510            s.serialize_u32(self.bits())
511        }
512    }
513};
514
515#[cfg(feature = "rkyv")]
516#[cfg_attr(all(docsrs, not(doctest)), doc(cfg(feature = "rkyv")))]
517const _: () = {
518    use rkyv::{
519        primitive::ArchivedU32, rancor::Fallible, Archive, Archived, Deserialize, Place, Serialize,
520    };
521
522    impl Archive for GameModsLegacy {
523        type Archived = Archived<u32>;
524        type Resolver = ();
525
526        fn resolve(&self, resolver: (), out: Place<Self::Archived>) {
527            self.bits().resolve(resolver, out);
528        }
529    }
530
531    impl<S: Fallible + ?Sized> Serialize<S> for GameModsLegacy {
532        fn serialize(&self, s: &mut S) -> Result<(), S::Error> {
533            self.bits().serialize(s)
534        }
535    }
536
537    impl<D: Fallible + ?Sized> Deserialize<GameModsLegacy, D> for ArchivedU32 {
538        fn deserialize(&self, _: &mut D) -> Result<GameModsLegacy, D::Error> {
539            Ok(GameModsLegacy::from_bits_retain(self.to_native()))
540        }
541    }
542};
543
544#[cfg(test)]
545mod tests {
546    use super::*;
547
548    #[test]
549    fn try_from_str() {
550        assert_eq!(
551            GameModsLegacy::from_str("Nm").unwrap(),
552            GameModsLegacy::NoMod
553        );
554        assert_eq!(
555            GameModsLegacy::from_str("hD").unwrap(),
556            GameModsLegacy::Hidden
557        );
558
559        let mods = GameModsLegacy::from_bits(24);
560        assert_eq!(GameModsLegacy::from_str("HRhD").unwrap(), mods);
561        assert!(GameModsLegacy::from_str("HHDR").is_err());
562    }
563
564    #[test]
565    fn iter() {
566        let mut iter = GameModsLegacy::default().iter();
567        assert_eq!(iter.next(), Some(GameModsLegacy::NoMod));
568        assert_eq!(iter.next(), None);
569
570        let mut iter = GameModsLegacy::from_bits(584).iter();
571        assert_eq!(iter.next(), Some(GameModsLegacy::Hidden));
572        assert_eq!(iter.next(), Some(GameModsLegacy::Nightcore));
573        assert_eq!(iter.next(), None);
574    }
575
576    #[cfg(feature = "serde")]
577    mod serde {
578        use super::*;
579
580        #[test]
581        fn deser_str() {
582            let json = r#""HDHR""#;
583            let mods = serde_json::from_str::<GameModsLegacy>(json).unwrap();
584            let expected = GameModsLegacy::Hidden | GameModsLegacy::HardRock;
585
586            assert_eq!(mods, expected);
587        }
588
589        #[test]
590        fn deser_bits() {
591            let json = "1096";
592            let mods = serde_json::from_str::<GameModsLegacy>(json).unwrap();
593            let expected =
594                GameModsLegacy::Hidden | GameModsLegacy::DoubleTime | GameModsLegacy::Flashlight;
595
596            assert_eq!(mods, expected);
597        }
598
599        #[test]
600        fn deser_seq() {
601            let json = r#"["NF", 2, { "acronym": "HT", "settings": { "any": true } }]"#;
602            let mods = serde_json::from_str::<GameModsLegacy>(json).unwrap();
603            let expected = GameModsLegacy::NoFail | GameModsLegacy::Easy | GameModsLegacy::HalfTime;
604
605            assert_eq!(mods, expected);
606        }
607    }
608}