rosu_pp/catch/
attributes.rs

1use std::mem;
2
3use crate::catch::performance::CatchPerformance;
4
5/// The result of a difficulty calculation on an osu!catch map.
6#[derive(Clone, Debug, Default, PartialEq)]
7pub struct CatchDifficultyAttributes {
8    /// The final star rating
9    pub stars: f64,
10    /// The approach rate.
11    pub ar: f64,
12    /// The amount of fruits.
13    pub n_fruits: u32,
14    /// The amount of droplets.
15    pub n_droplets: u32,
16    /// The amount of tiny droplets.
17    pub n_tiny_droplets: u32,
18    /// Whether the [`Beatmap`] was a convert i.e. an osu!standard map.
19    ///
20    /// [`Beatmap`]: crate::model::beatmap::Beatmap
21    pub is_convert: bool,
22}
23
24impl CatchDifficultyAttributes {
25    /// Return the maximum combo.
26    pub const fn max_combo(&self) -> u32 {
27        self.n_fruits + self.n_droplets
28    }
29
30    /// Whether the [`Beatmap`] was a convert i.e. an osu!standard map.
31    ///
32    /// [`Beatmap`]: crate::model::beatmap::Beatmap
33    pub const fn is_convert(&self) -> bool {
34        self.is_convert
35    }
36
37    /// Returns a builder for performance calculation.
38    pub fn performance<'a>(self) -> CatchPerformance<'a> {
39        self.into()
40    }
41
42    pub(crate) const fn set_object_count(&mut self, count: &ObjectCount) {
43        self.n_fruits = count.fruits;
44        self.n_droplets = count.droplets;
45        self.n_tiny_droplets = count.tiny_droplets;
46    }
47
48    pub(crate) const fn add_object_count(&mut self, count: GradualObjectCount) {
49        if count.fruit {
50            self.n_fruits += 1;
51        } else {
52            self.n_droplets += 1;
53        }
54
55        self.n_tiny_droplets += count.tiny_droplets;
56    }
57}
58
59/// The result of a performance calculation on an osu!catch map.
60#[derive(Clone, Debug, Default, PartialEq)]
61pub struct CatchPerformanceAttributes {
62    /// The difficulty attributes that were used for the performance calculation
63    pub difficulty: CatchDifficultyAttributes,
64    /// The final performance points.
65    pub pp: f64,
66}
67
68impl CatchPerformanceAttributes {
69    /// Return the star value.
70    pub const fn stars(&self) -> f64 {
71        self.difficulty.stars
72    }
73
74    /// Return the performance point value.
75    pub const fn pp(&self) -> f64 {
76        self.pp
77    }
78
79    /// Return the maximum combo of the map.
80    pub const fn max_combo(&self) -> u32 {
81        self.difficulty.max_combo()
82    }
83
84    /// Whether the [`Beatmap`] was a convert i.e. an osu!standard map.
85    ///
86    /// [`Beatmap`]: crate::model::beatmap::Beatmap
87    pub const fn is_convert(&self) -> bool {
88        self.difficulty.is_convert
89    }
90
91    /// Returns a builder for performance calculation.
92    pub fn performance<'a>(self) -> CatchPerformance<'a> {
93        self.difficulty.into()
94    }
95}
96
97impl From<CatchPerformanceAttributes> for CatchDifficultyAttributes {
98    fn from(attributes: CatchPerformanceAttributes) -> Self {
99        attributes.difficulty
100    }
101}
102
103#[derive(Clone, Default)]
104pub struct ObjectCount {
105    fruits: u32,
106    droplets: u32,
107    tiny_droplets: u32,
108}
109
110#[derive(Copy, Clone, Default)]
111pub struct GradualObjectCount {
112    fruit: bool,
113    tiny_droplets: u32,
114}
115
116pub enum ObjectCountBuilder {
117    Regular {
118        count: ObjectCount,
119        take: usize,
120    },
121    Gradual {
122        count: GradualObjectCount,
123        all: Vec<GradualObjectCount>,
124    },
125}
126
127impl ObjectCountBuilder {
128    pub fn new_regular(take: usize) -> Self {
129        Self::Regular {
130            count: ObjectCount::default(),
131            take,
132        }
133    }
134
135    pub fn new_gradual() -> Self {
136        Self::Gradual {
137            count: GradualObjectCount::default(),
138            all: Vec::with_capacity(512),
139        }
140    }
141
142    pub fn into_regular(self) -> ObjectCount {
143        if let Self::Regular { count, .. } = self {
144            count
145        } else {
146            unreachable!()
147        }
148    }
149
150    pub fn into_gradual(self) -> Vec<GradualObjectCount> {
151        if let Self::Gradual { all, .. } = self {
152            all
153        } else {
154            unreachable!()
155        }
156    }
157
158    pub fn record_fruit(&mut self) {
159        match self {
160            Self::Regular { count, take } => {
161                if *take > 0 {
162                    *take -= 1;
163                    count.fruits += 1;
164                }
165            }
166            Self::Gradual { count, all } => {
167                count.fruit = true;
168                all.push(mem::take(count));
169            }
170        }
171    }
172
173    pub fn record_droplet(&mut self) {
174        match self {
175            Self::Regular { count, take } => {
176                if *take > 0 {
177                    *take -= 1;
178                    count.droplets += 1;
179                }
180            }
181            Self::Gradual { count, all } => all.push(mem::take(count)),
182        }
183    }
184
185    pub const fn record_tiny_droplets(&mut self, n: u32) {
186        match self {
187            Self::Regular { count, take } => {
188                if *take > 0 {
189                    count.tiny_droplets += n;
190                }
191            }
192            Self::Gradual { count, .. } => count.tiny_droplets += n,
193        }
194    }
195}